Photo by Emily Morter on Unsplash
Recently I was asked to implement a page that needs to work with a fetching interval, as I wanted to keep the server state as simple as posible I decided to give react-query a try and implement a interval logic on it. Here is what I came up with it.
Use case
We need to make a post to an endpoint to start a process. Then every 5 seconds ask for its progress, once we received that the process is finished we must stop fetching.
As a plus I wanted to keep this on a hook in order to be used in different parts of the app. Lets start.🤓
Code
First we need to create a hook with a mutation inside in order to start the process. We add the stop state in order to tell us when to stop the fetching and we save the process id we get from the response.
const useProcessInterval = ({ onSuccess, onError }) => {
const [processId, setProcessId] = useState(null);
const [stop, setStop] = useState(false);
// Mutation to start the process
const { mutate } = useMutation(startProcess, {
onMutate: () => {
//When mutate we want to start fetching
setStop(false);
},
onError: error => {
console.error(error);
//Stop fetching
setStop(true);
onError();
},
onSuccess: data => {
setProcessId(data.process_id);
},
});
};
For the interval fetching query part, we are going to take advantage of the refetchInterval method we have with react-query.
//Fetch until received status is finished
const { isLoading, data } = useQuery(['processProgress', processId], getProgress, {
onSuccess: data => {
if (data.status === 'finished') {
// Stop fetching
setStop(true);
//Clean up processId
setProcessId(null);
onSuccess();
}
},
onError: error => {
console.error(error);
setStop(true);
setProcessId(null);
onError();
},
//Only start getting process when we have received the process id from the mutation
enabled: processId != null,
//Keep refetching every 5 seconds while we don't stop it
refetchInterval: stop ? false : 5000,
refetchIntervalInBackground: true,
refetchOnWindowFocus: false,
});
Finally we end up with the final hook.
const useProcessInterval = ({ onSuccess, onError }) => {
const [processId, setProcessId] = useState(null);
const [stop, setStop] = useState(false);
// Mutation to start the process
const { mutate } = useMutation(startProcess, {
onMutate: () => {
setStop(false);
},
onError: error => {
console.error(error);
setStop(true);
onError();
},
onSuccess: data => {
setProcessId(data.process_id);
},
});
//Fetch until received status is finished
const { isLoading, data } = useQuery(['processProgress', processId], getProgress, {
onSuccess: data => {
if (data.status === 'finished') {
setStop(true);
setProcessId(null);
onSuccess();
}
},
onError: error => {
console.error(error);
setStop(true);
setProcessId(null);
onError();
},
enabled: processId != null,
refetchInterval: stop ? false : 5000,
refetchIntervalInBackground: true,
refetchOnWindowFocus: false,
});
return { mutate, data, isLoading };
};
And we can use it in our component the following way.
const { mutate, data, isLoading } = useProcessInterval({
onSuccess: () => console.log('Process finished'),
onError: () => console.log('Error with process'),
});
See you in the next posts! 🖐
Top comments (6)
Good use-case, thanks for the article. I'd still like to point out that this is not long-polling in an http sense, where the server keeps the connection open until the information is available. What you are doing here is a "normal", traditional polling where the client initiates a new request at an interval level.
see: en.wikipedia.org/wiki/Push_technol...
Maybe you can point that out somewhere, because that is somewhat confusing :)
Thanks for the comment, totally agree in changing that to avoid confusion 👍
Anyone know what
startProcess
andgetProgress
are for?Also
useQuery
does not takeonSuccess
oronError
. So I'm not sure where they've come from.Docs: tanstack.com/query/latest/docs/fra...
This article no longer works in the latest version of React Query:
tkdodo.eu/blog/breaking-react-quer...
thanks
Thank you for this!