skip to Main Content

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


  1. Chosen as BEST ANSWER

    What I ended up doing:

    • Moved KeywordFilterControl and the handler function to a separate file so I could keep the 'use client' hooks there.
    • used useSearchParams in the filter control and searchParams in Page to put my selected keywords.
    export default function KeywordFilterControl(props) {
        const searchParams = useSearchParams();
        const pathname = usePathname();
        const { replace } = useRouter();
    
        function handleCheck(checked, term) {
            const params = new URLSearchParams(searchParams)
            if(checked && !params.has('keywords')) {
                params.set('keywords',','.concat(term,','))
            } else if (checked) {
                params.set('keywords',params.get('keywords')?.concat(',',term,','));
            } else {
                params.set('keywords',params.get('keywords')?.replace(','.concat(term,','),''));
            }
            replace(`${pathname}?${params.toString()}`);
    
          }
    
        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 => handleCheck(e.target.checked, keyword[0])}
                                    defaultChecked={searchParams.get('keywords')?.includes(keyword[0])}
                                    ></input>
                                {keyword[0]} ({keyword[1]})
                            </label>
                        </div>
                    );
                })}
            </ul>
        );
    }
    

  2. 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:

    • Page component is now not more async component
    • New state has been added
    • useEffect is used for async operation
    'use client'
    
    import { fetchDesignKeywords } from '@/app/lib/data';
    import { useState, useEffect } from 'react';
    
    function KeywordFilterControl(props) {
        return (
            <ul className="w-[180px] truncate">
                {props.dks.map(keyword => (
                    <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 function Page() {
        const [categoryFilters, setCategoryFilters] = useState(new Set());
    
        // declaring a new State for storing result of fetchDesignKeywords()
        const [dks, setDks] = useState([]);
    
        // using useEffect for performing async operation call
    
        useEffect(() => {
            const fetchData = async () => {
                try {
                    const data = await fetchDesignKeywords();
                    setDks(data);
                } catch (error) {
                    console.error('Error fetching data:', error);
                }
            };
    
            fetchData();
        }, []); 
    
        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>
        )
    }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search