如何解决如何在ReactJS中使用Checkbox在列表中应用过滤器?
我有一个列出一些产品的列表,这些产品分为几类。有些产品可能具有多个类别。
我正在尝试通过复选框对这些类别应用过滤器。当用户选中该复选框时,该列表必须使用选定的类别进行更新。
我仍然是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
并将activeFilter
和setActiveFilter
作为道具传递给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
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。