简体   繁体   中英

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:

过滤器屏幕截图

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 .

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:

编辑反应过滤器(分叉)

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM