When I click on the checkboxes, the filter works, but when I click again, the filter does not work and the checkbox flags also seem to work separately. I want to make that when clicked, when the checkbox was worked, filter this. Booleans need to be created for individual checkboxes?
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>
}
Thank you
CodePudding user response:
Simple Solution (with updated data model for category array)
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>
CodePudding user response:
Here is a minimal solution works without useEffect
or changing category
and products
.
A few issues that can be addressed in posted code:
- Each
input
need to have an uniqueid
which matcheshtmlFor
onlabel
for the pair to work together. - When
filterProducts
sets a new state value, it does not get the updated value in the same block. Only use this event to set the filters intoCheck
. - The value type of
toCheck
can an object of[category]: Boolean
so each category can be filtered separately, and filters can be combined as well. - The original
products
can be filtered before mapped, so there is no need to keep another state ofnewProducts
, or to modifyproducts
data.
Example (live demo: 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>
);
}
CodePudding user response:
You can get whether the checkbox is checked through e.target.checked
rather than using a state variable. You also don't need to attach a value, you can just pass it directly, not that it matters.
Since you're filtering on multiple elements, this pattern isn't going to work either. You'll need to save all of your filters so you can remove one but keep the others when the box is unchecked. I like to use a Set
for this purpose. You can then just filter the products on that set.
You also don't need to use state variables for constants that don't change.
Also, your id
s and htmlFor
s need to be unique, or clicking on a label will always select the first box. It's simpler just to wrap the input with the label so you don't need to worry about it.
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>
);
}