T
TanStack3y ago
conscious-sapphire

Using useQuery, setInterval and useEffect in the same component

Hello everyone! I'm pretty new in using react-query and I have a hopefully simple question: I have my useQuery function that is wrapped in a getItems function, then I have a setInterval that updates a component state every second, to display the current hour. The fact that the state related to the interval is updated re-triggers the render of the page, so it re-triggers the getItems function every time. Fortunately useQuery is powerful, so it's not doing API call to backend. But it keeps re-fetching the items object, so everything using this object is re-rendered. Before I was using useEffect for fetching the APIs, so the content of fetched items was kept, but now I've put my getItems ouside of useEffect. What should I do to resolve this problem? code:
const Dashboard = () => {
const [date, setDate] = useState(new Date());

useEffect(() => {
let today = setInterval(() => setDate(new Date()), 1000);
return function cleanup() {
clearInterval(today);
};
}, []);

// Obviously this line is re-triggered everytime "date" changes
const {items} = useGetItems();
const Dashboard = () => {
const [date, setDate] = useState(new Date());

useEffect(() => {
let today = setInterval(() => setDate(new Date()), 1000);
return function cleanup() {
clearInterval(today);
};
}, []);

// Obviously this line is re-triggered everytime "date" changes
const {items} = useGetItems();
8 Replies
conscious-sapphire
conscious-sapphireOP3y ago
Update: Solved it using useRef instead of useState. But if you have other hints that would be appreciated! The thing is: updating every state cause a re-trigger of the useQuery function, it doesn't seem like the perfect way to handle data from backend, expecially if those data doesn't change and it's only a state update.
inland-turquoise
inland-turquoise3y ago
Why don’t you you just have it a different component?
optimistic-gold
optimistic-gold3y ago
react query returns a stable reference for data whenever it's served from cache. So when you have const {data} = useQuery(...), data should remain stable. Can you show what's inside useGetItems()?
fair-rose
fair-rose3y ago
yeah I'd put the interval into the component that renders the timer to minimize the impact. but also: data returned from useQuery is referentially stable, so you'll always get back the same list of items. How is this different from the useEffect before?
conscious-sapphire
conscious-sapphireOP3y ago
Thank you all for the responses! Putting the timer in a different component is a great idea! I will share with you a bit of the structure of my code, so maybe you can give me some more hints:
// utils/useGetItems.tsx
export const useGetItems = (
params
): UseQueryResult<{
items
}> => {
return useQuery(
[ServerStateKeyEnum.Items],
() => apiCall("/getItems", "POST", params),
{
onSuccess: (data: { items }) => {
return data;
},
onError: (err: any) => {
useErrorState.getState().updateCode(err.status);
useErrorState.getState().updateMessage(err.statusText);
return err;
},
}
);
};

// Dashboard.tsx
const Dashboard = () => {
const [date, setDate] = useState(new Date());

useEffect(() => {
let today = setInterval(() => setDate(new Date()), 1000);
return function cleanup() {
clearInterval(today);
};
}, []);

// Obviously this line is re-triggered everytime "date" changes
const {items} = useGetItems();
// utils/useGetItems.tsx
export const useGetItems = (
params
): UseQueryResult<{
items
}> => {
return useQuery(
[ServerStateKeyEnum.Items],
() => apiCall("/getItems", "POST", params),
{
onSuccess: (data: { items }) => {
return data;
},
onError: (err: any) => {
useErrorState.getState().updateCode(err.status);
useErrorState.getState().updateMessage(err.statusText);
return err;
},
}
);
};

// Dashboard.tsx
const Dashboard = () => {
const [date, setDate] = useState(new Date());

useEffect(() => {
let today = setInterval(() => setDate(new Date()), 1000);
return function cleanup() {
clearInterval(today);
};
}, []);

// Obviously this line is re-triggered everytime "date" changes
const {items} = useGetItems();
I have some questions for you: - In your opinion is it good to handle the global error and the success in a separate file to make it re-usable and not duplicate code or it's better to put it into the same file of the component? - Is using useQuery this way useful related to the fact the data is stable? For @TkDodo 🔮 : the difference using useEffect is that putting the API call in useEffect is triggered only when the page is loaded (using useEffect(()=>{},[]), but using react-query at top level, will reTrigger useGetItems() at each re-render. P.s. Don't blame me for using POST requests to get content, not my design choice. Unfortunately the project is not mine and I'm only FE dev
fair-rose
fair-rose3y ago
but using react-query at top level, will reTrigger useGetItems() at each re-render.
it doesn't matter that useGetItems() is "triggered" again. It doesn't trigger a request just because it renders, and it doesn't give you a new object. It will give you exactly the same thing no matter how often it renders other things I'm seeing: - params should be part of your QueryKey. This is really important to avoid all sorts of issues - not sure what the onError handler is doing, but it seems like it copies errors somewhere? That shouldn't be necessary - onSuccess like that is a no-op. you can't return things from onSuccess
conscious-sapphire
conscious-sapphireOP3y ago
Thank you very much! Super helpful! Last questions and I will leave you in peace xD - What do you mean by onError not necessary? I have a global error state handling, rendering different components based on error, so I'm using a global state (through Zustand) to handle all sort of errors. Do you have better ways to do this? If you have some docs about it I would be glad to read them. - I'm encountering another issue, before I had a useState containing my object, so when I was updating a text field I updated the object's key I needed and then saved the new state. Being a text field, I can't do an API call with useMutation every time I insert a new letter. How do you handle this? I would create a state only for the text input, but for taking it simple I would prefer to edit the item state direcly, without adding states. EDIT: for second question I found your article (https://tkdodo.eu/blog/react-query-and-forms), I read it and sort of understood how to do it, but if you have some more explanations they are more than welcome! However, we don't have a form library yet, and being a big project, inserting it now would become difficult. Here, https://stackoverflow.com/questions/42550341/react-trigger-onchange-if-input-value-is-changing-by-state some folks suggest to use states to track local changes, what do you think about this?
Stack Overflow
React: trigger onChange if input value is changing by state?
Edit: I don't want to call handleChange only if the button has been clicked. It has nothing to do with handleClick. I gave an example in the @shubhakhatri answer's comment. I want to change the in...
fair-rose
fair-rose3y ago
for global error handling, I would use error boundaries or the global onError callback on the queryCache, depending on what you are doing with the error: https://tkdodo.eu/blog/react-query-error-handling
React Query Error Handling
After covering the sunshine cases of data fetching, it's time to look at situations where things don't go as planned and "Something went wrong..."

Did you find this page helpful?