T
TanStack2y ago
yappiest-sapphire

Suggestions on dynamic refetchInterval adjustment

I'm aware of the fact that refetchInterval takes a function to allow it to be adjusted, which is great. I'm wondering what the suggested/ideal approach is for the following scenario: What I'd like to achieve is to be able to trigger a more rapid refetch after a job is triggered by a button push, and then fall back to a more relaxed schedule once all the tasks associated with that job are complete. The idea here being that the UI should be more reactive when a job is ongoing, but in the interest of performance should slow down if nothing is happening. The issue I'm encountering is that because I'd like to use a more relaxed interval as my default (30 seconds), it takes a long time to trigger the recalculation to switch over to the more rapid interval (5 seconds). I'd like the switch to be instantaneous. I've tried using queryClient.invalidateQueries and queryClient.refetchQueries combined with the refetchIntervals function at the point that the button push occurs to try to immediately trigger a refetch and recalculate the interval as a result but I'm not getting the desired effect. The API call that triggers the jobs resolves immediately, but the jobs may take some indeterminate amount of time to run, so I have to monitor the results of queries to determine when everything has finished. Here is what I have so far:
function useProjectQuery() {
return useQuery({
queryKey: ['rootItem', 'project'],
queryFn: async () => {
let fetchedProject = await getProject();
fetchedProject = JSON.parse(fetchedProject);
return fetchedProject;
},
refetchInterval: data => ((data != undefined && data.status === "finished") ? 5000 : 30000)
});
}
function useProjectQuery() {
return useQuery({
queryKey: ['rootItem', 'project'],
queryFn: async () => {
let fetchedProject = await getProject();
fetchedProject = JSON.parse(fetchedProject);
return fetchedProject;
},
refetchInterval: data => ((data != undefined && data.status === "finished") ? 5000 : 30000)
});
}
I've also tried accomplishing this using state, by storing the refetchInterval in state and passing it to the hook; but although I can observe that the initial state is passed to the hook/query successfully, updates to the refetchInterval state using setRefetchInterval on button press appear to have no effect. I have also tried using state to set a custom useEffect+useRef+useInterval hook that monitors whether jobs are running and sets the interval accordingly. No luck there either. Any ideas or observations on how to accomplish this would be appreciated. Thank you.
3 Replies
yappiest-sapphire
yappiest-sapphireOP2y ago
Having messed with this a bit more, I think it was down to order of operations and not having awaited the promise that refetchQueries returned. It is now working much better after having done that, but I would be curious to know whether refetchQueries sounds like a good method of achieving what I'm after or if there's a better way. The last issue ended up being key inconsistencies. Once fixed everything works exactly as expected. Trying to use queryClient to invalidate/refetch and trigger the recalculation works reliably when setting a value (because there is a specific value to check against) but not 100% of the time when unsetting a value. Setting a fast refetch interval (1000ms) works all the time, but I don't want to poll that frequently for performance reasons. Is there any way to update the interval more directly rather than waiting on the query to set itself on the next refetch? Can I still do this using state?
flat-fuchsia
flat-fuchsia2y ago
I would trigger a refetch with refetchQueries when you push the button, that should re-trigger the refetchInterval function after the refetch completes
yappiest-sapphire
yappiest-sapphireOP2y ago
Thanks, that makes sense and seems to be working well. For that remaining issue with triggering a query after unsetting something, I think I may be getting tangled up by inconsistent keys. Would it be considered an anti-pattern to have useQueries process all queries in all scenarios, even where the input might be a string (by casting it as an array before passing it in for instance) or otherwise just an array with a single element? I can't verify one way or another whether this is related to keys or query functions being inconsistent because I can't get devtools running in the framework I'm using (framework's problem, not react query's). I will try to create a reproduction of just the affected components in codesandbox. For instance, would it be improper to have a situation where you use useQuery for creating queries against individual items and useQueries for arrays of items, creating a scenario where a useQuery hook and a useQueries hook could create identical queries with identical functions and keys? See below:
function useClipPropQuery(clip, fs, columnKey) {
return useQuery({
queryKey: ['clips', { type: 'props' }, clip.nodeId, columnKey],
queryFn: async () => {
if (columnKey === "origStatus") {
let clipObj = new Object();
const clipOrigProps = await getClipFs(clip.origPath, fs.data);
clipObj = {
...clip,
...clipOrigProps
};
clipObj.sizeLabel = sizeAdjust(clipObj.size, "include");
return clipObj;
} else {
let clipObj = new Object();
const clipProxyProps = await getClipFs(clip.proxyPath, fs.data);
clipObj = {
...clip,
...clipProxyProps
};
clipObj.sizeLabel = sizeAdjust(clipObj.size, "include");
return clipObj;
}
},
enabled: !!fs.isSuccess,
}
);
}

function useClipPropQueries(clips, fs, columnKey) {
return useQueries({
queries: !!(clips.isSuccess && fs.isSuccess)
? clips.data.map((clip) => {
return {
queryKey: ['clips', { type: 'props' }, clip.nodeId, columnKey],
queryFn: async () => {
if (columnKey === "origStatus") {
let clipObj = new Object();
const clipOrigProps = await getClipFs(clip.origPath, fs.data);
clipObj = {
...clip,
...clipOrigProps
};
clipObj.sizeLabel = sizeAdjust(clipObj.size, "include");
return clipObj;
} else {
let clipObj = new Object();
const clipProxyProps = await getClipFs(clip.proxyPath, fs.data);
clipObj = {
...clip,
...clipProxyProps
};
clipObj.sizeLabel = sizeAdjust(clipObj.size, "include");
return clipObj;
}
},
}
})
: [],
})
}
function useClipPropQuery(clip, fs, columnKey) {
return useQuery({
queryKey: ['clips', { type: 'props' }, clip.nodeId, columnKey],
queryFn: async () => {
if (columnKey === "origStatus") {
let clipObj = new Object();
const clipOrigProps = await getClipFs(clip.origPath, fs.data);
clipObj = {
...clip,
...clipOrigProps
};
clipObj.sizeLabel = sizeAdjust(clipObj.size, "include");
return clipObj;
} else {
let clipObj = new Object();
const clipProxyProps = await getClipFs(clip.proxyPath, fs.data);
clipObj = {
...clip,
...clipProxyProps
};
clipObj.sizeLabel = sizeAdjust(clipObj.size, "include");
return clipObj;
}
},
enabled: !!fs.isSuccess,
}
);
}

function useClipPropQueries(clips, fs, columnKey) {
return useQueries({
queries: !!(clips.isSuccess && fs.isSuccess)
? clips.data.map((clip) => {
return {
queryKey: ['clips', { type: 'props' }, clip.nodeId, columnKey],
queryFn: async () => {
if (columnKey === "origStatus") {
let clipObj = new Object();
const clipOrigProps = await getClipFs(clip.origPath, fs.data);
clipObj = {
...clip,
...clipOrigProps
};
clipObj.sizeLabel = sizeAdjust(clipObj.size, "include");
return clipObj;
} else {
let clipObj = new Object();
const clipProxyProps = await getClipFs(clip.proxyPath, fs.data);
clipObj = {
...clip,
...clipProxyProps
};
clipObj.sizeLabel = sizeAdjust(clipObj.size, "include");
return clipObj;
}
},
}
})
: [],
})
}
This is the result of different usage of the results of useClipsQuery in different components - where it is passed to useClipPropQuery individual elements of the array returned by the query have been passed to the component by an iterator in the parent component, whereas useClipPropQueries gets the original query result with the full array and all of the query object properties.

Did you find this page helpful?