reactjsreact-hooksreact-queryreact-data-table-component

Filtering data for first time not working with react-query


I have a basic ReactJS component that uses react-query ("@tanstack/react-query": "^4.24.6") and "react-data-table-component": "^7.5.3". The data gets loaded, I display it in a table. Standard stuff, works fine.

I had a search component as described here, which was working fine. During site UI redesign, I externalized the search box so it no longer sits within the data table component.

To let users search from the search box, I am doing what seems to be a simple 4-step process:

  1. Loading data remains the same:
const {data, isLoading} = useQuery(["whatever"], () => {
        return getDataFromBackend();
    }, {
        refetchInterval: 10000
    });
  1. I defined a new state variable, as follows:
const [filteredData, setFilteredData] = useState(data);
  1. I defined an onSearch function to handle changes to search box, and changed filteredData based on the search text:
    const onSearch = (searchText) => {
        if (!searchText || searchText.length === 0) {
            setFilteredData(data);
            return;
        }
        // Filter data based on search text
        const filteredData = rawFilesList.filter(item => {
            return (
                // Whatever logic needed to filter
            )
        });
        setFilteredData(filteredData);
    }

  1. And finally, I pass filteredData to the table:
<DataTable
    data={filteredData}
/>

This works as expected, EXCEPT when the react-query initial value is undefined (on first load or if I remove the query value using the dev tool). After query loads data, the "data" field gets updated, but it does not trigger the table to be reloaded. As soon as I type something into the search field, normal services are restored, but it is not clear to me why the filteredData variable does not get loaded correctly.

I tried adding an explicit setFilteredData, but that also did not help:

    if (isLoading) {
        return <>
            <div>Loading..</div>
        </>
    }

    setFilteredData(data);

I made the following change to step #1 above, and it seems to work, but I am not sure if this breaks the behavior of isLoading and other flags:

const {data, isLoading} = useQuery(["whatever"], async () => {
        const x = await getDataFromBackend();
        setFilteredData(x);
        return x;
    }, {
        refetchInterval: 10000
    });

Any help on getting the first load to work as expected would be appreciated.


Solution

  • That's because of how useState works:

    const { data } = useQuery(...)
    const [filteredData, setFilteredData] = useState(data);
    

    data will be undefined on the first render, so you pass undefined to useState. When data then comes in later, useState has already been initialized, so the changing value will be ignored.

    What you can do is either:

    Have one component that calls useQuery, render loading & error states, then pass it as initialFilteredData to another component (once data exists). There, you can put it into useState, because the component mounts later.

    An even better approach is:

    In your case, this could be as simple as:

    const { data } = useQuery(...)
    const [userData, setUserData] = useState();
    
    const filteredData = userData ?? data
    

    Note how we pass nothing to useState, so it will initialize with undefined, and then, we'll derive filteredData from that or the server value. This deriving happens on every render, so if data from useQuery changes, filteredData will pick it up (unless you already have something in your local state).