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:
const {data, isLoading} = useQuery(["whatever"], () => {
return getDataFromBackend();
}, {
refetchInterval: 10000
});
const [filteredData, setFilteredData] = useState(data);
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);
}
<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.
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:
useState
, only store what the user has changed.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).