How to filter and sort the same array of object state in redux?

前端 未结 1 1529
予麋鹿
予麋鹿 2020-12-07 23:57

I am working on movie collection app , where i have a page with movie list. I want to add the functionality of filtering the movie based on year, genre and rating and also i

相关标签:
1条回答
  • 2020-12-08 00:25

    First, we should create component to display movies list.
    Note, this is a pure stateless component, we are not making sorting or filtering there, just rendering derived props:

    // Movies.js
    
    import React from 'react';
    
    function Movies({ movies }) {
      return (
        <ul>
          {movies.map((m, i) =>
            <li key={i}>{m.year} - {m.title}.({m.genre}) - {m.rating}</li>
          )}
        </ul>
      );
    }
    
    export default Movies;
    

    Next we will create another stateless component, which will contain sorting and filters view.
    Here we are also rendering props, and providing callback to select elements:

    // Pane.js
    
    import React from 'react';
    
    function Pane({
      selectedYear,
      selectedGenre,
      selectedRating,
      years = [],
      genres = [],
      ratings = [],
      sorting,
      onYearChange,
      onGenreChange,
      onRatingChange,
      onSortingChange,
    }) {
      return (
        <div>
          <div>
            Filters:
            <div>
              Year:
              <select
                defaultValue={selectedYear}
                onChange={e => onYearChange(e.target.value)}
              >
                <option value="all" >All</option>
                {years.map((y, i) =>
                  <option key={i} value={y}>{y}</option>
                )}
              </select>
            </div>
            <div>
              Genre:
              <select
                defaultValue={selectedGenre}
                onChange={e => onGenreChange(e.target.value)}
              >
                <option value="all" >All</option>
                {genres.map((g, i) =>
                  <option key={i} value={g}>{g}</option>
                )}
              </select>
            </div>
            <div>
              Rating:
              <select
                defaultValue={selectedRating}
                onChange={e => onRatingChange(e.target.value)}
              >
                <option value="all" >All</option>
                {ratings.map((r, i) =>
                  <option key={i} value={r}>{r}</option>
                )}
              </select>
            </div>
          </div>
          <div>
            Select sorting:
            <select
              defaultValue={sorting}
              onChange={e => onSortingChange(e.target.value)}
            >
              <option value="alphabetically">Alphabetically</option>
              <option value="year">Year</option>
              <option value="rating">Rating</option>
            </select>
          </div>
        </div>
      );
    }
    
    export default Pane;
    

    We will store movies and all filtering and sorting data in app state. We need create reducer, to handle sorting and filtering actions:

    // reducers.js
    
    const items = [{
      title: 'Mad max',
      year: 2015,
      rating: 8,
      genre: 'fantasy',
    }, {
      title: 'Spider man 2',
      year: 2014,
      rating: 7,
      genre: 'fantasy',
    }, {
      title: 'Iron man 3',
      year: 2013,
      rating: 7,
      genre: 'fantasy',
    }, {
      title: 'Dumb and Dumber To',
      year: 2014,
      rating: 5,
      genre: 'comedy',
    }, {
      title: 'Ted 2',
      year: 2015,
      rating: 6,
      genre: 'comedy',
    }];
    
    export default function moviesReducer(state = {
      movies: items,
      year: 'all',
      rating: 'all',
      genre: 'all',
      sorting: 'year',
    }, action) {
      switch (action.type) {
        case 'SET_YEAR':
          return {
            ...state,
            year: action.year,
          };
        case 'SET_RATING':
          return {
            ...state,
            rating: action.rating,
          };
        case 'SET_GENRE':
          return {
            ...state,
            genre: action.genre,
          };
        case 'SET_SORTING':
          return {
            ...state,
            sorting: action.sorting,
          };
        default:
          return state;
      }
    }
    

    To provide data to stateless components, we should use containers.
    Let's create PaneContainer to provide sorting and filtering data to Pane component:

    // PaneContainer.js
    
    import { connect } from 'react-redux';
    import Pane from './Pane';
    
    // Simple helper function, which return all filters from state by given key.
    function getFilters(key, movies) {
      return movies.reduce((acc, movie) => {
        if (!acc.includes(movie[key])) {
          return [...acc, movie[key]];
        }
        return acc;
      }, []);
    }
    
    function mapStateToProps(state, props) {
      const { sorting, year, genre, rating } = state;
      return {
        selectedYear: year,
        selectedGenre: genre,
        selectedRating: rating,
        years: getFilters('year', state.movies),
        genres: getFilters('genre', state.movies),
        ratings: getFilters('rating', state.movies),
        sorting,
      };
    }
    
    function mapDispatchToProps(dispatch, props) {
      return {
        // Here, we are providing callbacks with dispatching functions.
        onYearChange(year) {
          dispatch({
            type: 'SET_YEAR',
            year,
          });
        },
        onGenreChange(genre) {
          dispatch({
            type: 'SET_GENRE',
            genre,
          });
        },
        onRatingChange(rating) {
          dispatch({
            type: 'SET_RATING',
            rating,
          });
        },
        onSortingChange(sorting) {
          dispatch({
            type: 'SET_SORTING',
            sorting,
          });
        },
      };
    }
    
    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(Pane);
    

    And finally, we will create MoviesContainer which will provide visible movies to Movies component:

    // MoviesContainer.js
    
    import { connect } from 'react-redux';
    import Movies from './Movies';
    
    // Getting visible movies from state.
    function getVisibleMovies(year, genre, rating, sorting, movies) {
      return movies
        .filter(m => {
          return (
            (year == 'all' || year == m.year) &&
            (genre == 'all' || genre == m.genre) &&
            (rating == 'all' || rating == m.rating)
          );
        })
        .sort((a, b) => {
          if (sorting == 'year') {
            return b.year - a.year;
          }
          if (sorting == 'rating') {
            return b.rating - a.rating;
          }
          if (sorting == 'alphabetically') {
            return a.title > b.title ? 1 : a.title < b.title ? -1 : 0;
          }
        });
    }
    
    function mapStateToProps(state) {
      const { year, genre, rating, sorting, movies } = state;
      return {
        movies: getVisibleMovies(year, genre, rating, sorting, movies),
      };
    }
    
    export default connect(mapStateToProps)(Movies);
    
    0 讨论(0)
提交回复
热议问题