I’m working on a web app in React.js which will have a main panel that displays items, and a side panel with checkboxes to filter the items. I’m currently attempting to pass the current set of selected checkboxes back to the parent page – once it’s there I can worry about pushing it through to the main display. For now I’m hoping to just display how many boxes are checked, so I know that something is getting through.
I looked at Filtering with checkboxes in React and got a rough sense of useState
, but it doesn’t seem to be working for me – I believe the issue I’m hitting is that my DB fetch has to be in an async Component, but then the state doesn’t get updated when I hit the checkbox, and I don’t know how to resolve this.
Here’s my code:
'use client'
import { fetchDesignKeywords } from '@/app/lib/data'; /* returns an Array of 2-element arrays */
import { useState } from 'react';
function KeywordFilterControl(props) {
return(
<ul className="w-[180px] truncate">
{props.dks.map(keyword => {
return(
<div key={keyword[0]}>
<label className="form-check-label" key={keyword[0]}>
<input className="form-check-input mr-2" id={keyword[0]} type="checkbox"
onChange={e => props.updateFilters(e.target.checked, keyword[0])}></input>
{keyword[0]} ({keyword[1]})
</label>
</div>
);
})}
</ul>
);
}
export default async function Page() {
let [categoryFilters, setCategoryFilters] = useState(new Set());
const dks = await fetchDesignKeywords()
function updateFilters(checked, categoryFilter) {
console.log(categoryFilter)
if (checked)
setCategoryFilters((prev) => new Set(prev).add(categoryFilter));
if (!checked)
setCategoryFilters((prev) => {
const next = new Set(prev);
next.delete(categoryFilter);
return next;
});
}
return (
<div>
<div className="flex flex-row">
<KeywordFilterControl
updateFilters={updateFilters}
dks={dks}/>
<p>Selected: {categoryFilters.size}</p>
</div>
</div>
)
}
What I’m hoping for is the console.log(categoryFilter)
to log something when I click a checkbox, and for the <p>Selected: {categoryFilters.size}</p>
to update the count. Neither of these is happening – nothing shows up in the console log, and the count remains at 0.
I have tried moving the const dks = await fetchDesignKeywords()
into the KeywordFilterControl
function and making that async instead, but that doesn’t seem to make any difference.
How do I make this work?
EDIT:
Adding a console.log(keyword);
statement in the map function is printing the full list of keywords to my console.
2
Answers
What I ended up doing:
KeywordFilterControl
and the handler function to a separate file so I could keep the 'use client' hooks there.useSearchParams
in the filter control andsearchParams
inPage
to put my selected keywords.The fetchDesignKeywords operation is asynchronous, and calling it directly in the component body can lead to unpredictable behavior, as React components are synchronous by nature.
Instead use useEffect hook for performing this operation as this is a common pattern to handle asynchronous operations in React components.
Secondly, in a React component, if a value is expected to change over time or trigger re-renders, it’s a good practice to store it in the component’s state. So, it’s better to use useState for storing the data being returned from fetchDesignKeywords. Below is the updated code for your reference.
Please Note following changes in the code: