Skip to content

Store copy of data state before applying and after clearing filter

I have a simple app that calls an API, returns the data (as an array of objects), sets a data state, and populates a few charts and graphs.

const loadData = async () => {
    const url = 'https://my-api/api/my-api';
    const response = await fetch(url);
    const result = await response.json();

    setData(result.data);
}

After setting the data, the data state is sent to every component and everything is populated. I created a filters pane that can filter the existing, populated data (for example, a gender filter that filters the data on the selected gender). What I did, and it’s obviously wrong, is created an onChange handler that filters the data to the selected gender then uses the setData (sent as a prop; also the state variable, data) to set the filtered data. When I clear the filter, the original, non-filtered data is replaced by the filtered data so the original data is lost.

const genderFilterHanlder = (e) => {
    const filteredData = data.filter(x => x.gender === e.target.value);
    setData(filteredData);
}

I tried creating an intermediary state the preserves the original data then upon clearing the filters, it sets the data (setData) to the original. But this breaks when I have a filter that allows you to choose multiple values (like multiple languages; I can choose one language, clear it successfully, but if I choose two languages, then clear one, it breaks as the data is now the first chosen filter data).

How would I go about this?

Answer

I’d leave data itself alone and have a separate filteredData state member that you set using an effect:

const [filteredData, setFilteredData] = useState(data);
const [filter, setFilter] = useState("");
// ...
useEffect(() => {
    const filteredData = filter ? data.filter(/*...apply filter...*/) : data;
    setFilteredData(filteredData);
}, [filter, data]); // <=== Note our dependencies
// ...
// ...render `filteredData`, not `data`...

Then your change handler just updates filter (setFilter(/*...the filter...*/)).

That way, any time the filter changes, or any time data changes, the data gets filtered and rendered.

Live Example:

const { useState, useEffect } = React;

const Child = ({data}) => {
    const [filteredData, setFilteredData] = useState(data);
    const [filter, setFilter] = useState("");

    useEffect(() => {
        if (!filter) {
            setFilteredData(data);
            return;
        }
        const lc = filter.toLocaleLowerCase();
        const filteredData = filter
            ? data.filter(element => element.toLocaleLowerCase().includes(lc))
            : data;
        setFilteredData(filteredData);
    }, [filter, data]); // <=== Note our dependencies
    
    return <div>
        <input type="text" value={filter} onChange={({currentTarget: {value}}) => setFilter(value)} />
        <ul>
            {filteredData.map(element => <li key={element}>{element}</li>)}
        </ul>
    </div>;
};

const greek = [
    "alpha",
    "beta",
    "gamma",
    "delta",
    "epsilon",
    "zeta",
    "eta",
    "theta",
    "iota",
    "kappa",
    "lambda",
    "mu",
    "nu",
    "xi",
    "omicron",
    "pi",
    "rho",
    "sigma",
    "tau",
    "upsilon",
    "phi",
    "chi",
    "psi",
    "omega",
];
const initialData = greek.slice(0, 4);
const Example = () => {
    const [data, setData] = useState(initialData);

    useEffect(() => {
        const handle = setInterval(() => {
            setData(data => {
                if (data.length < greek.length) {
                    return [...data, greek[data.length]];
                }
                clearInterval(handle);
                return data;
            });
        }, 800);
        return () => {
            clearInterval(handle);
        };
    }, []);

    return <Child data={data} />;
};

ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>