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

前端 未结 1 1535
予麋鹿
予麋鹿 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条回答
  •  猫巷女王i
    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 (
        
      {movies.map((m, i) =>
    • {m.year} - {m.title}.({m.genre}) - {m.rating}
    • )}
    ); } 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 (
        
    Filters:
    Year:
    Genre:
    Rating:
    Select sorting:
    ); } 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)
提交回复
热议问题