微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在ReactJS中使用Checkbox在列表中应用过滤器?

如何解决如何在ReactJS中使用Checkbox在列表中应用过滤器?

我有一个列出一些产品的列表,这些产品分为几类。有些产品可能具有多个类别。

我正在尝试通过复选框对这些类别应用过滤器。当用户选中该复选框时,该列表必须使用选定的类别进行更新。

enter image description here

我仍然是Redux的初学者,而且我不知道如何在组件之间进行通信以更新列表。我说组件之间的通信是因为类别列表在Drawer组件中,而产品列表在Card组件中。

我将代码放入codesandbox,因为其中包含很多文件

在这里显示我的产品列表:

import React,{ useState,useMemo,useEffect } from 'react';
import { useSelector } from 'react-redux';

import CardItem from '../CardItem';
import Pagination from '../Pagination';
import Search from '../Search';
import { useStyles } from './styles';

const Card = (props) => {
  const { activeFilter } = props;
  const classes = useStyles();
  const data = useSelector((state) => state.perfume.collections);
  const [searchPerfume,setSearchPerfume] = useState('');
  const [currentPage,setCurrentPage] = useState(1);
  const [perfumesPerPage,setPerfumesPerPage] = useState(3);

  console.log('activeFilter: ',activeFilter);

  const filteredPerfumes = useMemo(() => {
    return data.filter((perfume) =>
      perfume.name.toLowerCase().includes(searchPerfume.toLowerCase())
    );
  },[data,searchPerfume]);

  const currentPerfumes = filteredPerfumes.slice(
    (currentPage - 1) * perfumesPerPage,currentPage * perfumesPerPage
  );
  const pages = Math.ceil(filteredPerfumes.length / perfumesPerPage);

  useEffect(() => {
    if (currentPage > pages) {
      setCurrentPage(1);
    }
  },[currentPage,pages]);

  const pageNumbers = Array(pages)
    .fill(null)
    .map((val,index) => index + 1);

  const handleClick = (page) => {
    setCurrentPage(page);
  };

  return (
    <div>
      <Search
        data-testid="input-filter-id"
        setSearchPerfume={setSearchPerfume}
      />
      {currentPerfumes
        .filter((perfume) => {
          return (
            perfume.name.toLowerCase().indexOf(searchPerfume.toLowerCase()) >= 0
          );
        })
        .map((item) => (
          <CardItem key={item.id} item={item} />
        ))}
      <Pagination
        pageNumbers={pageNumbers}
        handleClick={handleClick}
        currentPage={currentPage}
      />
    </div>
  );
};

export default Card;

在这里,我正在显示类别列表:

import Divider from '@material-ui/core/Divider';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import CheckBox from '@material-ui/core/CheckBox';
import React,{ useState } from 'react';
import { useSelector } from 'react-redux';

import { useStyles } from './styles';

const DrawerComponent = (props) => {
  const { activeFilter,setActiveFilter } = props;
  const classes = useStyles();
  const data = useSelector((state) => state.perfume.collections);

  const handleChange = (text) => (event) => {
    setActiveFilter((prev) => ({
      ...prev,value: event.target.checked,text,}));
  };

  const allCategories = data
    .reduce((p,c) => [...p,...c.categories],[])
    .filter((elem,index,self) => index === self.indexOf(elem));

  return (
    <div className={classes.root}>
      <div className={classes.toolbar} />
      <Divider />
      <List className={classes.list}>
        {allCategories.sort().map((text,index) => (
          <ListItem className={classes.itemList} button key={text}>
            <CheckBox onChange={handleChange(text)} />
            <ListItemText primary={text} />
          </ListItem>
        ))}
      </List>
    </div>
  );
};

export default DrawerComponent;

您知道如何将该过滤器应用于列表吗?

非常感谢您。

解决方法

状态控制

通常,当您需要在组件之间进行通信时,解决方案是Lifting State Up。意味着状态应该由一个共同的祖先控制。

不要在您的DrawerComponent中使用它:

const [activeFilter,setActiveFilter] = React.useState([]);

将其移动到您的PerfumeStore并将activeFiltersetActiveFilter作为道具传递给DrawerComponent

您需要向onChange中的Checkbox组件添加DrawerComponent函数,该函数通过调用setActiveFilter来添加或删除类别。

因此,现在您需要将activeFilter应用于在Card中确定的香水清单。您可以将所有过滤条件上移至PerfumeStore,但为简单起见,我们将activeFilter作为Card的一种支持向下传递(它只需要读取但不需要编写过滤器,因此我们不会传递setActiveFilter)。现在,Card组件已具有根据所选类别过滤项目所需的信息。

Redux选择器

到目前为止,一切都与反应有关,而您使用redux的事实根本没有发挥作用。如果需要的话,您将合并redux原理的方法是将组件之外的一些过滤和映射逻辑定义为state和其他参数的选择器函数。然后,您可以使用选择器来调用useSelector,而选择器仅获取您需要的数据,而无需调用useSelector来获取在组件内部处理的大量状态。

DrawerComponent中可以找到类别列表,这是一个显而易见的地方。创建一个选择器函数getPerfumeCategories,该函数接受state并返回类别。然后在组件中,调用const allCategories = useSelector(getPerfumeCategories);

现在,此组件负责的所有工作就是呈现类别。它不再负责存储选择(我们已经将useState移出了)或从状态中查找类别。很好!如果您想更深入地了解这样做的好处,则可以阅读诸如“单一责任原则”,“关注点分离”,“逻辑与演示”组件之类的原则。

Card中,您可以使用选择器来获取已经过滤的香水列表。但是在这种情况下,getCurrentPagePerfumes函数将采用许多不同的参数,因此有点混乱。

编辑:过滤

您已寻求有关如何应用activeFilter的值来过滤列表中显示的香水的帮助。

可以一次选择多个类别,因此activeFilter需要标识所有处于活动状态的类别的全部。我首先提出了一个名称数组,但是从数组中删除项目(不带突变)比为对象分配值更为复杂。

因此,我想到了一个对象,其中键是类别名称,值是是否检查类别的boolean是/否。这使handleChange非常简单,因为我们可以将该键的值更新为event.target.checked的值。

  const handleChange = (text) => (event) => {
    setActiveFilter((prev) => ({
      ...prev,[text]: event.target.checked,}));
  };
  • ...prev说:“除了要更改的键外,其他所有内容都保持相同。”
  • [text]说:“我要更新的键是变量text,而不是文字键'text'”
  • event.target.checked是是否选中此类别的布尔值。

我们可以为activeFilter设置一个初始状态,其中包括每个类别的键,并且所有值均为false(即未选择任何内容)。或者我们可以假设对象不完整,前提是假设不包含键,则不对其进行检查。让我们开始吧。

因此,现在我们的activeFilter看起来像:{Floral: true,Floriental: false,Fresh: true},其中有些是true,有些是false,很多东西都丢失了,因此假定为{{1} }。

我们需要弄清楚如何根据false的值过滤显示的香水。让我们从编写一个确定是否有资格展示一种香水的函数开始,然后我们可以将其用作香水阵列上array.filter()的回调。如果选中了香水的任何类别,我们希望将其包括在内(除非您希望它与所有选中的类别相匹配?)。看起来像:

activeFilter

.some()在各个类别之间循环,如果任何类别的回调为 perfume.categories.some( category => activeFilter[category] === true ); ,则返回true

我将此添加到您的true备忘录中,并添加了filteredPerfumes作为依赖项,以便在复选框更改时重新过滤。

activeFilter

那行得通,只是没有检查类别时什么也没显示-哎呀!因此,我们要添加一个特殊情况,说明“如果未检查类别,则所有香水都会通过类别过滤器”。为此,我们需要知道是否有选中的类别。有很多方法可以做到这一点,但这是一种:

  const filteredPerfumes = useMemo(() => {
    return data.filter((perfume) =>
      perfume.name.toLowerCase().includes(searchPerfume.toLowerCase())
      && perfume.categories.some( 
        category => activeFilter[category] === true
      )
    );
  },[data,searchPerfume,activeFilter]);

我们查看const hasCategoryFilter = Object.values(activeFilter).includes(true); 对象中的所有值,并查看其中是否包含activeFilter

现在,我们需要使用此值来仅根据类别true进行过滤。我将把前面的逻辑放入一个函数中并添加一个true语句(注意:布尔运算符if的使用时间较短,但我认为||更具可读性)。 / p>

if

旁注:我们有两个独立的过滤器,一个用于搜索,一个用于类别,因此我们可以调用 const matchesCategories = (perfume) => { if ( hasCategoryFilter ) { return perfume.categories.some( category => activeFilter[category] === true ); } else return true; } 一次,一次或两次检查两个条件,然后分别检查每个条件。不管你做什么。

最终的过滤器是:

data.filter

Updated Sandbox

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。