Когда я нажимаю на флажки, фильтр работает, но когда я нажимаю снова, фильтр не работает, и флаги флажков, похоже, также работают отдельно. Я хочу сделать так, чтобы при нажатии, когда флажок сработал, отфильтровать это. Булевы значения нужно создавать для отдельных флажков?
const App = () => {
const [category, setCategory] = useState(["electronics","jewelery","men's clothing","women's clothing"]);
const [products, setProducts] = useState([
{"id":1,"title":"Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops","price":109.95,"category":"electronics", "rating":{"rate":3.9,"count":120}},
{"id":2,"title":"Mens Casual Premium Slim Fit T-Shirts ","price":22.3, "category":"men's clothing","rating":{"rate":4.1,"count":259}},
{"id":3,"title":"Mens Cotton Jacket","price":55.99, "category":"men's clothing","rating":{"rate":4.7,"count":500}},
{"id":4,"title":"Womens Dress","price":15.99, "category":"women's clothing" ,"rating":{"rate":2.1,"count":430}},
{"id":5,"title":"John Hardy Women's Legends Naga Gold & Silver Dragon Station Chain Bracelet","price":695, "category":"jewelery","rating":{"rate":4.6,"count":400}}
])
let [newProducts, setNewProducts] = useState([])
let [toCheck, setToCheck] = useState(true);
const filterProducts = (value) => {
setToCheck(!toCheck);
if (toCheck) {
newProducts = products
setNewProducts([...newProducts.filter((a) => a.category == value)])
}
}
return <div>
<div className='d-flex justify-content-evenly'>
{
category.map((elm, index) => {
return <div className = "form-check ms-2" key = {index}>
<input className = "form-check-input" type = "checkbox" value = {elm} id = "flexCheckDefault" onChange = {(e) => filterProducts(e.target.value)}/>
<label className = "form-check-label" htmlFor = "flexCheckDefault">
{elm}
</label>
</div>
})
}
</div>
<div className='d-flex flex-wrap'>
{
(newProducts.length == 0 ? products : newProducts).map((prod) => {
return <div className='card m-3' style = {{ width: "400px" }} key = {prod.id}>
<div className='card-body'>
<p className='card-text'>Id: {prod.id}</p>
<h3 className='card-title'>Title: {prod.title}</h3>
<p className='card-text'>Price: {prod.price}</p>
<p className='card-text'>Category: {prod.category}</p>
<p className='card-text'>Rating: {prod.rating.rate}</p>
</div>
</div>
})
}
</div>
</div>
}
Спасибо
🤔 А знаете ли вы, что...
React обладает активным сообществом разработчиков и множеством сторонних библиотек и компонентов.
Вы можете узнать, установлен ли флажок, с помощью e.target.checked
, а не с помощью переменной состояния. Вам также не нужно прикреплять значение, вы можете просто передать его напрямую, но это не имеет значения.
Поскольку вы фильтруете несколько элементов, этот шаблон также не будет работать. Вам нужно будет сохранить все ваши фильтры, чтобы вы могли удалить один, но оставить остальные, когда флажок не установлен. Мне нравится использовать Set
для этой цели. Затем вы можете просто отфильтровать продукты в этом наборе.
Вам также не нужно использовать переменные состояния для констант, которые не изменяются.
Кроме того, ваши id
и htmlFor
должны быть уникальными, иначе нажатие на метку всегда будет выбирать первое поле. Проще просто обернуть ввод меткой, поэтому вам не нужно об этом беспокоиться.
Stackblitz: https://stackblitz.com/edit/react-ts-9a9n67?file=App.tsx
const categories = [
'electronics',
'jewelery',
"men's clothing",
"women's clothing",
];
const allProducts = [
{
id: 1,
title: 'Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops',
price: 109.95,
category: 'electronics',
rating: { rate: 3.9, count: 120 },
},
{
id: 2,
title: 'Mens Casual Premium Slim Fit T-Shirts ',
price: 22.3,
category: "men's clothing",
rating: { rate: 4.1, count: 259 },
},
{
id: 3,
title: 'Mens Cotton Jacket',
price: 55.99,
category: "men's clothing",
rating: { rate: 4.7, count: 500 },
},
{
id: 4,
title: 'Womens Dress',
price: 15.99,
category: "women's clothing",
rating: { rate: 2.1, count: 430 },
},
{
id: 5,
title:
"John Hardy Women's Legends Naga Gold & Silver Dragon Station Chain Bracelet",
price: 695,
category: 'jewelery',
rating: { rate: 4.6, count: 400 },
},
];
export default function App() {
let [categoryFilters, setcategoryFilters] = useState(new Set());
function updateFilters(checked, categoryFilter) {
if (checked)
setcategoryFilters((prev) => new Set(prev).add(categoryFilter));
if (!checked)
setcategoryFilters((prev) => {
const next = new Set(prev);
next.delete(categoryFilter);
return next;
});
}
const filteredProducts =
categoryFilters.size === 0
? allProducts
: allProducts.filter((p) => categoryFilters.has(p.category));
return (
<div>
<div className = "d-flex justify-content-evenly">
{categories.map((elm, index) => {
return (
<div className = "form-check ms-2" key = {index}>
<label className = "form-check-label">
<input
className = "form-check-input"
type = "checkbox"
onChange = {(e) => updateFilters(e.target.checked, elm)}
/>
{elm}
</label>
</div>
);
})}
</div>
<div className = "d-flex flex-wrap">
{filteredProducts.map((prod) => {
return (
<div className = "card m-3" style = {{ width: '400px' }} key = {prod.id}>
<div className = "card-body">
<p className = "card-text">Id: {prod.id}</p>
<h3 className = "card-title">Title: {prod.title}</h3>
<p className = "card-text">Price: {prod.price}</p>
<p className = "card-text">Category: {prod.category}</p>
<p className = "card-text">Rating: {prod.rating.rate}</p>
</div>
</div>
);
})}
</div>
</div>
);
}
Простое решение (с обновленной моделью данных для массива категорий)
const [category, setCategory] = useState([{value : "electronics", isChecked: false},{value : "jewelery", isChecked: false},{value : "men's clothing", isChecked: false},{value : "women's clothing", isChecked: false}]);
const [products, setProducts] = useState([
{"id":1,"title":"Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops","price":109.95,"category":"electronics", "rating":{"rate":3.9,"count":120}},
{"id":2,"title":"Mens Casual Premium Slim Fit T-Shirts ","price":22.3, "category":"men's clothing","rating":{"rate":4.1,"count":259}},
{"id":3,"title":"Mens Cotton Jacket","price":55.99, "category":"men's clothing","rating":{"rate":4.7,"count":500}},
{"id":4,"title":"Womens Dress","price":15.99, "category":"women's clothing" ,"rating":{"rate":2.1,"count":430}},
{"id":5,"title":"John Hardy Women's Legends Naga Gold & Silver Dragon Station Chain Bracelet","price":695, "category":"jewelery","rating":{"rate":4.6,"count":400}}
])
let [newProducts, setNewProducts] = useState([]);
const filterProducts = (e) => {
const categories = [...category];
const cat = categories.filter((cat)=>cat.value === e.target.value);
cat[0].isChecked = e.target.checked;
setCategory([...categories]);
const tempProduct = [];
for(let c of categories){
if (c.isChecked){
tempProduct.push(...products.filter((a) => a.category === c.value));
}
}
setNewProducts([...tempProduct]);
}
return <div>
<div className='d-flex justify-content-evenly'>
{
category.map((elm, index) => {
return <div className = "form-check ms-2" key = {index}>
<input className = "form-check-input" type = "checkbox" value = {elm.value} id = "flexCheckDefault" onChange = {filterProducts}/>
<label className = "form-check-label" htmlFor = "flexCheckDefault">
{elm.value}
</label>
</div>
})
}
</div>
<div className='d-flex flex-wrap'>
{
(newProducts.length === 0 ? products : newProducts).map((prod) => {
return <div className='card m-3' style = {{ width: "400px" }} key = {prod.id}>
<div className='card-body'>
<p className='card-text'>Id: {prod.id}</p>
<h3 className='card-title'>Title: {prod.title}</h3>
<p className='card-text'>Price: {prod.price}</p>
<p className='card-text'>Category: {prod.category}</p>
<p className='card-text'>Rating: {prod.rating.rate}</p>
</div>
</div>
})
}
</div>
</div>
Вот минимальное решение работает без useEffect
или изменения category
и products
.
Несколько проблем, которые можно решить в опубликованном коде:
input
должен быть уникальный id
, который соответствует htmlFor
на label
, чтобы пара работала вместе.filterProducts
устанавливает новое значение состояния, оно не получает обновленное значение в том же блоке. Используйте это событие только для установки фильтров в toCheck
.toCheck
может быть объектом [category]: Boolean
, поэтому каждую категорию можно фильтровать отдельно, а также фильтры можно комбинировать.products
можно отфильтровать перед сопоставлением, поэтому нет необходимости сохранять другое состояние newProducts
или изменять products
данные.Пример (живая демонстрация: stackblitz)
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [category, setCategory] = useState([
'electronics',
'jewelery',
"men's clothing",
"women's clothing",
]);
const [products, setProducts] = useState([
{
id: 1,
title: 'Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops',
price: 109.95,
category: 'electronics',
rating: { rate: 3.9, count: 120 },
},
{
id: 2,
title: 'Mens Casual Premium Slim Fit T-Shirts ',
price: 22.3,
category: "men's clothing",
rating: { rate: 4.1, count: 259 },
},
{
id: 3,
title: 'Mens Cotton Jacket',
price: 55.99,
category: "men's clothing",
rating: { rate: 4.7, count: 500 },
},
{
id: 4,
title: 'Womens Dress',
price: 15.99,
category: "women's clothing",
rating: { rate: 2.1, count: 430 },
},
{
id: 5,
title:
"John Hardy Women's Legends Naga Gold & Silver Dragon Station Chain Bracelet",
price: 695,
category: 'jewelery',
rating: { rate: 4.6, count: 400 },
},
]);
const [toCheck, setToCheck] = useState({});
const filterProducts = (value) =>
setToCheck((prev) => {
return { ...prev, [value]: !!!prev[value] };
});
return (
<div>
<div className = "d-flex justify-content-evenly">
{category.map((elm, index) => {
return (
<div className = "form-check ms-2" key = {index}>
<input
className = "form-check-input"
type = "checkbox"
value = {elm}
id = {`flexCheckDefault-${index}`}
onChange = {(e) => filterProducts(e.target.value)}
/>
<label
className = "form-check-label"
htmlFor = {`flexCheckDefault-${index}`}
>
{elm}
</label>
</div>
);
})}
</div>
<div className = "d-flex flex-wrap">
{products
.filter((prod) =>
Object.keys(toCheck).length === 0 ? true : !!toCheck[prod.category]
)
.map((prod) => {
return (
<div
className = "card m-3"
style = {{ width: '400px' }}
key = {prod.id}
>
<div className = "card-body">
<p className = "card-text">Id: {prod.id}</p>
<h3 className = "card-title">Title: {prod.title}</h3>
<p className = "card-text">Price: {prod.price}</p>
<p className = "card-text">Category: {prod.category}</p>
<p className = "card-text">Rating: {prod.rating.rate}</p>
</div>
</div>
);
})}
</div>
</div>
);
}