Reputation: 364
I have a useApi custom hook, that takes an endpoint (url) with multiple parameters. When one of the parameters changes a graph is rendered. The problem is that when one parameter changes, another parameter is changing as well and the graph is rendered twice. How can I solve it? Thanks
const useApi = (endpoint, requestType, body) => {
const [data, setData] = useState({ fetchedData: [], isError: false, isFetchingData: false });
useEffect(() => {
requestApi();
}, [endpoint]);
const requestApi = async () => {
let response = {};
try {
setData({ ...data, isFetchingData: true });
console.log(endpoint);
switch (requestType) {
case 'GET':
return (response = await axios.get(endpoint));
case 'POST':
return (response = await axios.post(endpoint, body));
case 'DELETE':
return (response = await axios.delete(endpoint));
case 'UPDATE':
return (response = await axios.put(endpoint, body));
case 'PATCH':
return (response = await axios.patch(endpoint, body));
default:
return (response = await axios.get(endpoint));
}
} catch (e) {
console.error(e);
setData({ ...data, isError: true });
} finally {
if (response.data) {
setData({ ...data, isFetchingData: false, fetchedData: response.data.mainData });
}
}
};
return data;
};
Upvotes: 1
Views: 1268
Reputation: 7917
There are a couple of places that can be refactored:
First, you can get rid of data
dependency in your useEffect
by converting it to something like this:
setData(currentData => {
return { ...currentData, isFetchingData: true }
})
As second and most importantly, you should either move your requestApi
function to inside of the useEffect
or wrap it with a useCallback
function.
And finally, It is totally OK if there are multiple renders followed by another. Because you are depending on all params inside of your useEffect
.
One thing that you can do is canceling the axios requests during the unmount by taking advantage of returning a function in useEffect.
So here is the final version of your code:
const useApi = (endpoint, requestType, body) => {
const [data, setData] = useState({
fetchedData: [],
isError: false,
isFetchingData: false
})
useEffect(() => {
let axiosSource = axios.CancelToken.source() // generate a source for axios
let didCancel = false // we can rely on this variable.
const requestApi = async () => {
let response = {}
try {
setData(data => {
return { ...data, isFetchingData: true }
})
console.log(endpoint)
const axiosOptions = { cancelToken: axiosSource.token }
switch (requestType) {
case 'GET':
return (response = await axios.get(endpoint, axiosOptions))
case 'POST':
return (response = await axios.post(endpoint, body, axiosOptions))
case 'DELETE':
return (response = await axios.delete(endpoint, axiosOptions))
case 'UPDATE':
return (response = await axios.put(endpoint, body, axiosOptions))
case 'PATCH':
return (response = await axios.patch(endpoint, body, axiosOptions))
default:
return (response = await axios.get(endpoint, axiosOptions))
}
} catch (e) {
console.error(e)
if (!didCancel) {
setData(data => {
return { ...data, isError: true }
})
}
} finally {
// do not update the data if the request is cancelled
if (response.data && !didCancel) {
setData(data => {
return {
...data,
isFetchingData: false,
fetchedData: response.data.mainData
}
})
}
}
}
requestApi()
// Here we are saying to axios cancel all current ongoing requests
// since this is the cleanup time.
return () => {
didCancel = true
axiosSource.cancel()
}
}, [body, endpoint, requestType])
return data
}
I did not test the code. But it should be working. Please try and tell me what happened.
Upvotes: 1