What cleaup function can be applied to this useEffect hook? - reactjs

I can't figure out what cleanup function can I apply in this useEffect hook.It is working fine without a cleanup function.
useEffect(() => {
const fetchProfileUser = async () => {
if (profileUserId === existingUser._id) {
setprofileUser(existingUser);
} else {
const profileUser = await UserApi.getUser(profileUserId);
setprofileUser(profileUser);
}
};
fetchProfileUser();
});

You don't always need a cleanup function. But most of the time you need dependency array in useEffect, even it is empty. If you are doing requests, you definitely need an dependency array.
If you are doing request, you can use AbortController to cancel the request using cleanup function, if that component gets unmounted.

Related

My custom React hook method "useFetch" is running 8 times when called

Hope anyone is able to help me with a custom react hook.
My custom react hook "useFetch" is running 8 times when called.
Can anyone see, why it is running 8 times when the custom "useFetch" hook is called?
I am a bit new to React, but it seems like I am using useEffect method wrong. Or maybe I need to use another method.
UseFetch hook method:
import React, { useState, useEffect } from "react";
export const useFetch = function (
options = {
IsPending: true,
},
data = {}
) {
// load data
const [loadData, setLoadData] = useState(null);
// pending
const [isPending, setIsPending] = useState(false);
// error
const [isError, setIsError] = useState(false);
useEffect(() => {
// method
const fetchData = async function () {
// try
try {
// set pending
setIsPending(true);
// response
const response = await fetch(data.url, data);
// handle errors
if (response.status !== 200 && response.status !== 201) {
// throw new error with returned error messages
throw new Error(`Unable to fetch. ${response.statusText}`);
}
// convert to json
const json = await response.json();
// set load data
setLoadData(json);
// set error
setIsError(false);
// set pending
setIsPending(false);
// catch errors
} catch (err) {
// set error
setIsError(`Error fetching data: ${err.message}`);
// set pending
setIsPending(false);
}
};
// invoke fetch data method
fetchData();
}, []);
// return
return {
loadData,
isPending,
isError,
};
};
export default useFetch;
Everytime you change a state in a hook, the component that has the hook in it will rerender, making it call the function again.
So let's start counting the renders/rerenders by the change of state:
Component mounted
setIsPending(true)
setLoadData(json)
setIsPending(false)
(depending if it's successful or not you might get more state changes, and therefore rerenders, and therefore hook being called again)
So 4 is not 8, so why are you getting 8?
I presume you are using React18, and React18 on development and StrictMode will call your useEffect hooks twice on mount: React Hooks: useEffect() is called twice even if an empty array is used as an argument
What can you do to avoid this?
First of all, check on the network tab how many times you are actually fetching the data, I presume is not more than 2.
But even so you probably don't want to fetch the data 2 times, even though this behaviour won't be on production and will only be on development. For this we can use the useEffect cleanup function + a ref.
const hasDataFetched = useRef(false);
useEffect(() => {
// check if data has been fetched
if (!hasDataFetched.current) {
const fetchData = async function () {
// fetch data logic in here
};
fetchData();
}
// cleanup function
return () => {
// set has data fetched to true
hasDataFetched.current = true;
};
}, []);
Or as you suggested, we can also add data to the dependency array. Adding a variable to a dependency array means the useEffect will only be triggered again, when the value of the variable inside the dependency array has changed.
(Noting that data is the argument you pass to the useFetch hook and not the actual data you get from the fetch, maybe think about renaming this property to something more clear).
useEffect(() => {
// check if data has been fetched
const fetchData = async function () {
// fetch data logic in here
};
fetchData();
}, [data]);
This will make it so, that only if loadData has not been fetched, then it will fetch it. This will make it so that you only have 4 rerenders and 1 fetch.
(There is a good guide on useEffect on the React18 Docs: https://beta.reactjs.org/learn/synchronizing-with-effects)
Every time you change the state within the hook, the parent component that calls the hooks will re-render, which will cause the hook to run again. Now, the empty array in your useEffect dependency should be preventing the logic of the hook from getting called again, but the hook itself will run.

useEffect hook is not waiting for an async depency

I'm using useEffect hook to implement some logic after an async function that contains an API call return an array of objects which is the the dependecy of the hook.
The problem is that the hook itself is not waiting for the array to change in order to execute the logic inside of it, it just executes even if availableSites is still []:
const [availableSites, setAvailableSites] = useState([]);
useEffect(initialize, [getAxiosInstance])
async function initialize() {
// ...
const initPage = async () => {
try {
const response = await getAxiosInstance().get(GetObjects); // Api EndPoint
if (response.data) {
setAvailableSites(response.data); // data for availableSites
const Secondresponse = await somePromise(); // Another API call thas lasts around 10 seconds
if (response && response.status === 200) {
// ...
}
}
} catch {
// ...
}
};
initPage();
}
// useEffect that is failing
useEffect(() => {
// I want this to happen when availableSites has recieved the data
}, [availableSites]);
useEffect is always going to execute at least once, doesn't matter what dependencies it has. If you only want it to execute when the data is set, use something like:
useEffect(() => {
if(availableSites.length > 0) {
doSomething...
}
}, [availableSites]);
If availableSites coming from the backend could be empty, set the initial state of availableSites to null and check if it's not null in the useEffect
If we have multiple instances of useEffect in the component, all the useEffect functions will be executed in the same order as they are defined inside the component the behavior you're getting is expected. Try checking if the array length has changed since the second useEffect depends on the [availableSites] so it will run again if the values of [availableSites] change.

Infinite re-render in functional react component

I am trying to set the state of a variable "workspace", but when I console log the data I get an infinite loop. I am calling the axios "get" function inside of useEffect(), and console logging outside of this loop, so I don't know what is triggering all the re-renders. I have not found an answer to my specific problem in this question. Here's my code:
function WorkspaceDynamic({ match }) {
const [proposals, setProposals] = useState([{}]);
useEffect(() => {
getItems();
});
const getItems = async () => {
const proposalsList = await axios.get(
"http://localhost:5000/api/proposals"
);
setProposals(proposalsList.data);
};
const [workspace, setWorkspace] = useState({});
function findWorkspace() {
proposals.map((workspace) => {
if (workspace._id === match.params.id) {
setWorkspace(workspace);
}
});
}
Does anyone see what might be causing the re-render? Thanks!
The effect hook runs every render cycle, and one without a dependency array will execute its callback every render cycle. If the effect callback updates state, i.e. proposals, then another render cycle is enqueued, thus creating render looping.
If you want to only run effect once when the component mounts then use an empty dependency array.
useEffect(() => {
getItems();
}, []);
If you want it to only run at certain time, like if the match param updates, then include a dependency in the array.
useEffect(() => {
getItems();
}, [match]);
Your use of useEffect is not correct. If you do not include a dependency array, it gets called every time the component renders. As a result your useEffect is called which causes setProposals then it again causes useEffect to run and so on
try this
useEffect(() => {
getItems();
} , []); // an empty array means it will be called once only
I think it's the following: useEffect should have a second param [] to make sure it's executed only once. that is:
useEffect(() => {
getItems();
}, []);
otherwise setProposal will modify the state which will trigger a re-render, which will call useEffect, which will make the async call, which will setProposal, ...

How to fix multiple call fetch data in forEach() using React Hooks

In react Hooks, I am trying to fetch data from the API array but in the Foreach function, the API call causes infinity.
How to fix this?
const [symbols, setSymbols] = useState([]);
getPortfolioSymbolList(portfolio_name).then(data => data.json()).then(res => {
res.forEach((symbol_data)=>{
fetchPrice(symbol_data.symbol).then(price => {
setSymbols(price);
});
})
}
function fetchPrice(symbol){
const price = fetch(`api_url`)
.then(chart => chart.json())
return price;
}
Here, call fetchPrice() causes in infinite.
Setting the state will always cause a rerender
What happens in your code is the request is made and then the data is set causing a rerender. Then because of the rerender the request is made again and sets the state again and causes the rerender again.
If you have a request for data you probably want to put a React.useEffect so it only requests once.
React.useEffect(() => {
/* your data request and data set */
}, []); // the [] will only fire on mount.
Is is because your setSymbols call inside forEach makes component rerender (reload) - it means that all of your main component function is call again and again... getPortfolioSymbolList too. You have to use useEffect hook to resolve this problem. Your getPortfolioSymbolList() API call should be inside useEffect.
https://reactjs.org/docs/hooks-effect.html
PROBLEM
Your first symbol is updated in your API call, which triggers a re-render of the component calling the API call to go on an infinite loop.
SOLUTION
Wrap your API in your useEffect. The function inside your useEffect will only be called once. See useEffect docs here
You need to use for await of to loop asynchronously. forEach can't loop asynchronously. See for await of docs here
Update your symbols once all the data is collected.
function Symbols() {
const [symbols, setSymbols] = useState([]);
React.useEffect(() => {
async function fetchSymbols() {
const portfolio = await getPortfolioSymbolList(portfolio_name);
const jsonPortfolios = await data.json();
const symbols = [];
for await (let jsonPortfolio of jsonPortfolios) {
const price = await fetchPrice(jsonPortfolio.symbol);
symbols.push(price);
}
setSymbols(symbols);
}
fetchSymbols();
}, []);
return /** JSX **/
}

React Hooks , function fetchData is not a react component?

I'm playing around with react hooks and I ran into a weird issue trying to fetch some data, when i'm creating a file to fetch data using hooks and axios
this works
import axios from 'axios';
const useResources = (resource) => {
const [resources, setResources ] = useState([]);
useEffect(
() => {
(async resource => {
const response = await axios.get(`randomapi.com/${resource}`);
setResources(response.data);
})(resource);
},
[resource]
);
return resources;
};
export default useResources;
but this doesn't
import { useState, useEffect } from 'react';
import Axios from 'axios';
const fetchData = () => {
const [data, setData] = useState('');
useEffect( async () => {
const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
setData(response.data);
});
return data;
};
export default fetchData;
'React Hook useEffect contains a call to 'setData'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [] as a second argument to the useEffect Hook.'
Aren't they the same?
On first glance, they are similar, but they still have differences.
Let's check them:
useEffect(
() => {
// we create a function, that works like a "black box" for useEffect
(async resource => {
const response = await axios.get(`randomapi.com/${resource}`);
// we don't use `setResources` in dependencies array, as it's used in wrapped function
setResources(response.data);
// We call this function with the definite argument
})(resource);
// this callback doesn't return anything, it returns `undefined`
},
// our function depends on this argument, if resource is changed, `useEffect` will be invoked
[resource]
);
useEffect hook should receive a function, which can return another function to dispose of all dynamic data, listeners (e.g. remove event listeners, clear timeout callbacks, etc.)
Next example:
// this function returns a promise, but actually useEffect needs a function,
// which will be called to dispose of resources.
// To fix it, it's better to wrap this function
// (how it was implemented in the first example)
useEffect( async () => {
const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
setData(response.data);
// we don't set up an array with dependencies.
// It means this `useEffect` will be called each time after the component gets rerendered.
// To fix it, it's better to define dependencies
});
So, we have 2 major errors:
1) Our useEffect callback returns a Promise instead of function, which implements dispose pattern
2) We missed dependencies array. By this reason component will call useEffect callback after each update.
Let's fix them:
useEffect(() => {// we wrap async function:
(async () => {
// we define resource directly in callback, so we don't have dependencies:
const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
setData(response.data);
})()
}, []);
Finally, we have fixed our second example :)
I even made an example with custom fetchData hook and final version of useEffect: https://codesandbox.io/s/autumn-thunder-1i3ti
More about dependencies and refactoring hints for useEffect you can check here: https://github.com/facebook/react/issues/14920

Resources