Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filter array of objects through checkboxes

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.

  • Rating
  • Genre

Screenshot:

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:

Edit react-filter

repository link.

like image 469
Ven Nilson Avatar asked May 30 '21 18:05

Ven Nilson


People also ask

How do you filter an array of objects?

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.

How do I create a checkbox filter?

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.

What is filtering with checkboxes?

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.

How to filter an array of objects in JavaScript?

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.

What is checkbox filtering in AngularJS?

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?

How do you use filterobjsinarr?

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.


Video Answer


2 Answers

So I had to do a couple changes in your code in order to address for proper filtering:

  1. Added a value key on both listCheckboxesRating and listCheckboxesGenre to use it for filtering.
export const listCheckboxesGenre = [
  {
    id: 0,
    name: 'genre',
    label: 'Any Genre',
    value: ''
  },
  ...
}
  1. updated onChangeHandler to pass value back to handleFilters instead of the id
const onChangeHandler = (checkboxId) => {
    // ...
    handleFilters(newState.map((id) => list[id].value));
};
  1. Created a couple filter functions to filter movies.
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:

Edit react-filter (forked)

like image 123
Luis Sardon Avatar answered Oct 22 '22 22:10

Luis Sardon


I did some refactoring.

  1. Made data more generic.
    export const listCheckboxesRating = {
      key: 'rating',
      data: [
        {
          id: 1,
          name: 'rating1',
          label: 'Rating 1',
          value: 1
        },
        ...
    }
  1. Refactor Checkbox component and just pass data.
    <Checkboxes
      list={listCheckboxesGenre}
      handleFilters={handleFilters}
    />
  1. Made filtration process work with value and key.
    // 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

like image 38
Zhang TianYu Avatar answered Oct 22 '22 23:10

Zhang TianYu