So I have a function that fetches from an API until it hits a 200 code.
Something like this:
/* These functions are in a separate file */
const fetchFile = async () => {
try {
const resp = await fetch('someUrl');
return resp.data;
} catch (err) {
if(err.response.code === 404){
await sleep(10); //sleep is a custom fn that awaits for N seconds
return fetchFile();
}else{
return 'Error';
}
}
}
const fetchAll = async (setData, setError) => {
const data = await fetchFile();
if(data === 'Error') {
setError('Sorry, error ocurred');
return;
}
setData(data);
}
/* React component */
const [data, setData] = useState([]);
const [error, setError] = useState('');
<button onClick={fetchAll}>Start fetch</button>
And I need to have another button that let's the user stop the recursing function. I know a common way of doing this is having a flag and check the value every time I call the recursing function, but since this is React, how can I achieve this? Should I have the functions inside the component file? Use a global window variable? Use localstorage? What's the best way?
CodePudding user response:
You can do couple of ways, here is one the way using useRef
/* These functions are in a separate file */
const fetchFile = async (cancellationRef) => {
try {
const resp = await fetch('someUrl');
return resp.data;
} catch (err) {
if(err.response.code === 404){
if(cancellationRef.current !==true){
await sleep(10); //sleep is a custom fn that awaits for N seconds
return fetchFile();
}
else{
return 'Request cancelled';
}
}else{
return 'Error';
}
}
}
const fetchAll = async (setData, setError,cancellationRef) => {
const data = await fetchFile(cancellationRef);
if(data === 'Error') {
setError('Sorry, error ocurred');
return;
}
setData(data);
}
/* React component */
const [data, setData] = useState([]);
const cancellationRef = useRef(false);
const [error, setError] = useState('');
<button onClick={fetchAll}>Start fetch</button>
<button onClick={()=>cancellationRef.current =true)}>Stop looping</button>
CodePudding user response:
Here is a full example - I recommend encapsulating the fetching behavior in a hook like this. An AbortController is used for cancelling any ongoing request. This takes advantage of useEffect
and its cleanup function, which is described in the react docs:
If your effect returns a function, React will run it when it is time to clean up:
/** Continuously makes requests until a non-404 response is received */
const fetchFile = async (url, signal) => {
try {
const resp = await fetch(url, { signal });
return resp.data;
} catch (err) {
if(err.response.code === 404){
await sleep(10);
return fetchFile(url, signal);
}
// there was an actual error - rethrow
throw err;
}
}
/** Hook for fetching data and cancelling the request */
const useDataFetcher = (isFetching) => {
// I advise using `null` as default values
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
if (isFetching) {
const controller = new AbortController();
fetchFile('someUrl', controller.signal)
.then(result => {
setData(result);
setError(null);
})
.catch(err => {
if (err.name === "AbortError") {
// request was aborted, reset state
setData(null);
setError(null);
} else {
setError(err);
}
});
// This cleanup is called every time the hook reruns (any time isFetching changes)
return () => {
if (!controller.signal.aborted) {
controller.abort();
}
}
}
}, [isFetching]);
return { data, error };
}
const MyComponent = () => {
const [isFetching, setIsFetching] = useState(false);
const { data, error } = useDataFetcher(isFetching);
const startFetch = useCallback(() => setIsFetching(true), []);
const cancelFetch = useCallback(() => setIsFetching(false), []);
useEffect(() => {
if (data || error) {
setIsFetching(false);
}
}, [data, error];
return isFetching
? <button onClick={cancelFetch}>Cancel</button>
: <button onClick={startFetch}>Start fetch</button>
}