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);