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.
Related
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.
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.
How to clean up the function retrieve in cleanup
i am not able to clean up pls help
const retrieve = async()=>{
const value = await AsyncStorage.getItem('users');
if (value !== null) {
// console.log("data inside async storagein deregister screen",value);
var replace_brackets = value.replace("["," ").replace("]","")
setList(JSON.parse(value))
}
}
useEffect(() => {
retrieve()
return function cleanup(){
retrieve()
}
})
Inside you app/any other component add:
useEffect(() => {
retrieve();
},[])
The above code basically runs like componentDidMount.
The issue is probably how you have defined your useEffect
useEffect(() => {
retrieve()
return function cleanup(){
retrieve()
}
})
The above useEffect that you have written will execute on every render. On load it will call retrieve and inside retrieve you are setting state so it may re-trigger this effect in an infinite loop.
By simply adding the dependencies to useEffect or keeping it empty if you want it to run only on page load you may not require a clean up function here.
try this.
useEffect(() => {
retrieve();
}, []) //Note empty brackets here for executing only on mount
I've gote some react component like below. I can use "messages" in return, but if I try to access messages inside some function, or useEffect, as in example, I always become initial value. How can I solve it in functional component? Thanks
const Messages = () => {
const { websocket } = useContext(WebsocketsContext);
let [ messages, setMessages ] = useState([]);
useEffect(() => {
getMessages()
.then(result => {
setMessages(result);
})
}, []);
useEffect(() => {
if(websocket != null){
websocket.onmessage = (msg) => {
let wsData = JSON.parse(msg.data);
if(wsData.message_type == 'Refresh'){
console.log(messages)
};
};
};
}, [websocket]);
return(
<div>...</div>
);
};
export default Messages;
Looks like you have encountered a stale closure
the useEffect with [websockets] in its dependency array will only ever "update" whenever the websocket reference/value changes. Whenever it does, the function will have created a "closure" around messages at that point in time. Thus, the value of messages will stay as is within that closure. If messages updates after websocket has been created, it will never update the value of "messages" within the onmessage callback function.
To fix this, add "messages" to the dependency array. [websockets, messages]. This will ensure the useEffect callback always has the latest state of messages, and this the onmessage function will have the latest state of messages.
useEffect(() => {
if(websocket != null){
websocket.onmessage = (msg) => {
let wsData = JSON.parse(msg.data);
if(wsData.message_type == 'Refresh'){
console.log(messages)
};
};
};
}, [websocket, messages]);
It's because your getMessages() is an async function. The order is as follows: component mounts initially and values are initialized -> componentDidMount() is invoked meaning your getMessages() is invoked (an async function!) -> your webaocket is initialized and invokes the second useEffect, which reads the initial value of messages -> your getMessages gets its response and sets the messages accordingly.
To make it work as intended, make the second useEffect's dependency array as [websocket, messages].
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 **/
}