This is a React.js
filter problem and I'm stuck here. The problem has static data of movies as an array of objects and we have to filter that data according to the checked state of different checkboxes.
Static Data:
export const data = [
{
id: 1,
title: 'Batman',
rating: 1,
genre: 'Action'
},
{
id: 2,
title: 'Superman',
rating: 5,
genre: 'Comedy'
},
{
id: 3,
title: 'Spiderman',
rating: 3,
genre: 'Thriller'
},
{
id: 4,
title: 'BananaMan',
rating: 2,
genre: 'Action'
},
{
id: 5,
title: 'OrangeMan',
rating: 4,
genre: 'Drama'
}
];
The abstract UI has the following picture that represents 2 types of filters.
Screenshot:
Both Filters are rendered on the UI as checkboxes. So, I've created a component called Checkboxes
which will take a prop named list
and it is an array of objects.
This list
represents unique ids
of checkboxes which will help us to track the checked
or unchecked
state of the checkbox. The different checkboxes id
will save inside the state which is an array.
const [checkedArray, setCheckedArray] = useState([]);
Finally, I'm rendering this Checkboxes
component twice inside the parent component called App.js
. One for rating and the other for genre.
List:
export const listCheckboxesRating = [
{
id: 0,
name: 'rating',
label: 'Any Rating'
},
{
id: 1,
name: 'rating1',
label: 'Rating 1'
},
{
id: 2,
name: 'rating2',
label: 'Rating 2'
},
{
id: 3,
name: 'rating3',
label: 'Rating 3'
},
{
id: 4,
name: 'rating4',
label: 'Rating 4'
},
{
id: 5,
name: 'rating5',
label: 'Rating 5'
}
];
export const listCheckboxesGenre = [
{
id: 0,
name: 'genre',
label: 'Any Genre'
},
{
id: 1,
name: 'action',
label: 'Action'
},
{
id: 2,
name: 'comedy',
label: 'Comedy'
},
{
id: 3,
name: 'drama',
label: 'Drama'
},
{
id: 4,
name: 'thriller',
label: 'Thriller'
}
];
Checkboxes component:
import {useState} from 'react';
import {handleToggle} from '../../utils';
const Checkboxes = ({list, handleFilters}) => {
const [checkedArray, setCheckedArray] = useState([]);
const onChangeHandler = (checkboxId) => {
const newState = handleToggle(checkboxId, checkedArray);
setCheckedArray(newState);
// Update this checked information into Parent Component
handleFilters(newState);
};
return list.map((item, index) => {
return (
<div key={index}>
<input
type="checkbox"
id={item.name}
checked={checkedArray.indexOf(item.id) !== -1}
onChange={() => onChangeHandler(item.id)}
/>
<label htmlFor={item.name}>{item.label}</label>
</div>
);
});
};
export default Checkboxes;
I'm passing the whole state of these checkboxes through a function named handleFilters
to parent component App.js
. You can see this line of code.
handleFilters(newState);
The App.js
component is saving states of these checkboxes with something like this:
const [selected, setSelected] = useState({
rating: [],
genre: []
});
App.js component:
import {useState} from 'react';
import Checkboxes from './Checkboxes';
import {data, listCheckboxesRating, listCheckboxesGenre} from '../data';
const App = () => {
const [movies, setMovies] = useState(data);
const [selected, setSelected] = useState({
rating: [],
genre: []
});
/**
* This function will perform the filtration process based on the key value.
*
* @param {number[]} checkboxState - It will take the final state of checkboxes
* @param {string} key - It will help us to determine the type of filtration
*/
const handleFilters = (checkboxState, key) => {
const newFilters = {...selected};
newFilters[key] = checkboxState;
// Filtration process
for (let key in newFilters) {
if (
newFilters.hasOwnProperty(key) &&
Array.isArray(newFilters[key]) &&
newFilters[key].length > 0
) {
if (key === 'rating') {
} else if (key === 'genre') {
}
}
}
// Save the filtered movies and update the state.
// setMovies();
setSelected(newFilters);
};
return (
<div>
{movies.map((movie) => (
<div key={movie.id}>
<div>Name: {movie.title}</div>
<div>Genre :{movie.genre}</div>
<div>Rating: {movie.rating}</div>
<hr />
</div>
))}
<div className="row">
<div className="col">
<h1>Filter by Rating</h1>
<Checkboxes
list={listCheckboxesRating}
handleFilters={(checkboxState) =>
handleFilters(checkboxState, 'rating')
}
/>
</div>
<div className="col">
<h1>Filter by Genre</h1>
<Checkboxes
list={listCheckboxesGenre}
handleFilters={(checkboxState) =>
handleFilters(checkboxState, 'genre')
}
/>
</div>
</div>
</div>
);
};
export default App;
The checkboxes checked
or unchecked
state of both filters are working fine and correctly updated with ids
inside the parent selected
state with something like this.
const [selected, setSelected] = useState({
rating: [1,2,3],
genre: [2,4]
});
But here is the confusing part where I'm stuck. How to map these checkboxes ids
and filter the data according to these ids
?
I've defined a function in the App.js
component handleFilters(checkboxState, key)
in which I wanted to iterate the selected
state to perform the filtration process but my mind is not making logic and I need help.
WORKING DEMO:
repository link.
One can use filter() function in JavaScript to filter the object array based on attributes. The filter() function will return a new array containing all the array elements that pass the given condition. If no elements pass the condition it returns an empty array.
Clicking the checkbox next to a value will add a filter for that value and its associated category. If possible, values should be accompanied by a number in parentheses that represents the number of items in the set that match that value.
The idea of filtering with checkboxes means that you have discrete values associated with the checkboxes, when checked, and you want to compare those checked values against properties within an array of objects. Sounds straight-forward, right? The first hurdle is handling checkboxes in Angular.
Filter an Array of Objects in JavaScript. JavaScript arrays have a filter () method that let you create a new array containing only elements that pass a certain test. In other words, filter () gives you a new array containing just the elements you need.
Angular’s custom Pipes can, really, achieve nearly any filtering you’d want. The idea of filtering with checkboxes means that you have discrete values associated with the checkboxes, when checked, and you want to compare those checked values against properties within an array of objects. Sounds straight-forward, right?
The filterObjsinArr function takes in an array of objects: arr and a selection array as arguments. It maps through arr to get individual objects. For each object, it loops through each property, checking if it exists in the selection array. This second method is shorter in terms of lines of code.
So I had to do a couple changes in your code in order to address for proper filtering:
value
key on both listCheckboxesRating
and listCheckboxesGenre
to use it for filtering.export const listCheckboxesGenre = [
{
id: 0,
name: 'genre',
label: 'Any Genre',
value: ''
},
...
}
onChangeHandler
to pass value
back to handleFilters
instead of the id
const onChangeHandler = (checkboxId) => {
// ...
handleFilters(newState.map((id) => list[id].value));
};
const handleFilters = (checkboxState, key) => {
const newFilters = { ...selected };
newFilters[key] = checkboxState;
const hasFilters =
newFilters.rating.length > 0 || newFilters.genre.length > 0;
const filterRating = (module) =>
newFilters.rating.includes(0) ||
newFilters.rating.includes(module.rating);
const filterGenre = (module) =>
newFilters.genre.includes('') || newFilters.genre.includes(module.genre);
const filteredMovies = data.filter(
(m) => !hasFilters || filterRating(m) || filterGenre(m)
);
setMovies(filteredMovies);
setSelected(newFilters);
};
WORKING DEMO:
I did some refactoring.
export const listCheckboxesRating = {
key: 'rating',
data: [
{
id: 1,
name: 'rating1',
label: 'Rating 1',
value: 1
},
...
}
<Checkboxes
list={listCheckboxesGenre}
handleFilters={handleFilters}
/>
// Filtration process
let filteredMovies = data.filter((movie) => {
for (let key in newFilters) {
if (
newFilters.hasOwnProperty(key) &&
Array.isArray(newFilters[key]) &&
newFilters[key].length > 0
) {
if (
newFilters[key].findIndex((value) => movie[key] === value) === -1
) {
return false;
}
}
}
return true;
});
Working DEMO: https://codesandbox.io/s/react-filter-forked-gs2b2?file=/src/components/App.js
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With