useEffect runs infinite loop despite no change in dependencies - reactjs

I have Function Component that utilizes hooks. In the useEffect hook, I simply want to fetch data from my back end and store the results in state. However, despite adding the data variable as a dependency, useEffect still fires on an infinite loop - even though the data hasn't changed. How can I stop useEffect from firing continuously?
I've tried the empty array hack, which DOES stop useEffect from continuously firing, but it's not the desired behavior. If the user saves new data, for example, useEffect should fire again to get updated data - I'm not looking to emulate componentDidMount.
const Invoices = () => {
const [invoiceData, setInvoiceData] = useState([]);
useEffect(() => {
const updateInvoiceData = async () => {
const results = await api.invoice.findData();
setInvoiceData(results);
};
updateInvoiceData();
}, [invoiceData]);
return (
<Table entries={invoiceData} />
);
};
I expected useEffect to fire after the initial render, and again ONLY when invoiceData changes.

The way the useEffect dependency array works is by checking for strict (===) equivalency between all of the items in the array from the previous render and the new render. Therefore, putting an array into your useEffect dependency array is extremely hairy because array comparison with === checks equivalency by reference not by contents.
const foo = [1, 2, 3];
const bar = foo;
foo === bar; // true
const foo = [1, 2, 3];
const bar = [1, 2, 3];
foo === bar; // false
Inside of your effect function, when you do setInvoiceData(results) you are updating invoiceData to a new array. Even if all the items inside of that new array are exactly the same, the reference to the new invoiceData array has changed, causing the dependencies of the effect to differ, triggering the function again -- ad infinitum.
One simple solution is to simply remove invoiceData from the dependency array. In this way, the useEffect function basically acts similar to componentDidMount in that it will trigger once and only once when the component first renders.
useEffect(() => {
const updateInvoiceData = async () => {
const results = await api.invoice.findData();
setInvoiceData(results);
};
updateInvoiceData();
}, []);
This pattern is so common (and useful) that it is even mentioned in the official React Hooks API documentation:
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.

Credit to jered for the great "under the hood" explanation; I also found Milind's suggestion to separate out the update method from useEffect to be particularly fruitful.
My solution, truncated for brevity, is as follows -
const Invoices = () => {
const [invoiceData, setInvoiceData] = useState([]);
useEffect(() => {
updateInvoiceData();
}, []);
// Extracting this method made it accessible for context/prop-drilling
const updateInvoiceData = async () => {
const results = await api.invoice.findData();
setInvoiceData(results);
};
return (
<div>
<OtherComponentThatUpdatesData handleUpdateState={updateInvoiceData} />
<Table entries={invoiceData} />
</div>
);
};

What's happening, is that when you update the invoiceData, that technically changes the state of invoiceData, which you have watched by the useEffect hook, which causes the hook to run again, which updates invoiceData. If you want useEffect to run on mount, which I suspect, then pass an empty array to the second parameter of useEffect, which simulates componentDidMount in class components. Then, you'll be able to update the local UI state with your useState hook.

I totally agree with Jared's answer.
But for some scenarios where you really want to not have reference comparison, then useDeepCompareEffect from react-use library is really good
https://github.com/streamich/react-use/blob/HEAD/docs/useDeepCompareEffect.md

Related

React make an async call on data after retrieving it from the state

I need to make an async call after I get some data from a custom hook. My problem is that when I do it causes an infinite loop.
export function useFarmInfo(): {
[chainId in ChainId]: StakingBasic[];
} {
return {
[ChainId.MATIC]: Object.values(useDefaultFarmList()[ChainId.MATIC]),
[ChainId.MUMBAI]: [],
};
}
// hook to grab state from the state
const lpFarms = useFarmInfo();
const dualFarms = useDualFarmInfo();
//Memoize the pairs
const pairLists = useMemo(() => {
const stakingPairLists = lpFarms[chainIdOrDefault].map((item) => item.pair);
const dualPairLists = dualFarms[chainIdOrDefault].map((item) => item.pair);
return stakingPairLists.concat(dualPairLists);
}, [chainIdOrDefault, lpFarms, dualFarms]);
//Grab the bulk data results from the web
useEffect(() => {
getBulkPairData(pairLists).then((data) => setBulkPairs(data));
}, [pairLists]);
I think whats happening is that when I set the state it re-renders which causes hook to grab the farms from the state to be reset, and it creates an infinite loop.
I tried to move the getBulkPairData into the memoized function, but that's not meant to handle promises.
How do I properly make an async call after retrieving data from my hooks?
I am not sure if I can give you a solution to your problem, but I can give you some hints on how to find out the cause:
First you can find out if the useEffect hook gets triggered too often because its dependency changes too often, or if the components that contains your code gets re-mounted over and over again:
Remove the dependency of your useEffect hook and see if it still gets triggered too often. If so, your problem lies outside of your component.
If not, find out if the dependencies of your useMemo hook change unexpectedly:
useEffect(()=>console.log("chainIdOrDefault changed"), [chainIdOrDefault]);
useEffect(()=>console.log("lpFarms changed"), [lpFarms]);
useEffect(()=>console.log("dualFarms changed"), [dualFarms]);
I assume, this is the most likely reason - maybe useFarmInfo or useDualFarmInfo create new objects on each render (even if these objects contain the same data on each render, they might not be identical). If so, either change these hooks and add some memoization (if you have access to your code) or narrow down the dependencies of your pairLists:
const pairLists = useMemo(() => {
const stakingPairLists = lpFarms[chainIdOrDefault].map((item) => item.pair);
const dualPairLists = dualFarms[chainIdOrDefault].map((item) => item.pair);
return stakingPairLists.concat(dualPairLists);
}, [lpFarms[chainIdOrDefault], dualFarms[chainIdOrDefault]]);

useCallback vs useEffect in React

What's the different between useEffect when you pass it dependencies as the second parameter and useCallback?
Don't both essentially run the function/code passed as the first parameter whenever the dependencies passed as the second parameter change?
From what I've read the two hooks are intended to serve different purposes, but my question is whether they in actuality could be used interchangeably because they functionally do the same thing
They're too different.
useEffect will run the function inside when the dependency array changes.
useCallback will create a new function when the dependency array changes.
You can't switch useEffect with useCallback alone because you also need the logic to run the newly created function. (I suppose you could implement this if you used a ref as well, but that'd be quite strange.)
You can't switch useCallback with useEffect because you very often don't want to run the newly created function immediately - rather, you usually want to pass it as a prop to some other component.
useCallback primarily exists for optimization purposes, to reduce re-renders of a child component.
No, They are not same.
useEffect - is used to run side effects in the component when something changes. useEffect does
not return you anything. It just runs a piece of code in the component.
useCallback - Whereas useCallback returns a function, it does not execute the code actually. It is important to understand that
functions are objects in Javascript. If you don't use useCallback, the function you define inside the component is
re-created whenever the component rebuilds.
Example
Consider this example, this component will go in a infinite loop. Think Why?
const TestComponent = props => {
const testFunction = () => {
// does something.
};
useEffect(() => {
testFunction();
// The effect calls testFunction, hence it should declare it as a dependency
// Otherwise, if something about testFunction changes (e.g. the data it uses), the effect would run the outdated version of testFunction
}, [testFunction]);
};
Because on each render the testFunction
would be re-created and we already know that ueEffect will run the code when ever the testFunction changes. And since testFunction changes on each render, the useEffect will keep on running, and hence an infinite loop.
To fix this, we have to tell react, hey please don't re-create the testFunction on each render, create it only on first render (or when something changes on which it depends).
const TestComponent = props => {
const testFunction = useCallback(() => {
// does something.
}, []);
useEffect(() => {
testFunction();
// The effect calls testFunction, hence it should declare it as a dependency
// Otherwise, if something about testFunction changes (e.g. the data it uses), the effect would run the outdated version of testFunction
}, [testFunction]);
};
This won't be a infinite loop, since instance of testFunction will change only on first render and hence useEffect will run only once.
useEffect will run the function inside when the dependency array changes.
useCallback will create a new function when the dependency array changes.
Let's take an example, If I run the below code and click the first button it'll always rerender MemoComponent as well. Why because every time
we are passing new onClick function to this. To avoid re-rendering of MemoComponent what we can do is wrap onClick to useCallback. Whenever you want to create a new function pass state to the dependence array.
If you want to perform some action on state change you can write inside useEffect.
const Button = ({ onClick }) => {
console.log("Render");
return <button onClick={onClick}>Click</button>;
};
const MemoComponent = React.memo(Button);
export default function Home() {
const [state, setState] = useState(1);
useEffect(() => {
console.log(state); // this will execute when state changes
}, [state]);
const onClick = () => {};
// const onClick = useCallback(() => {},[])
return (
<main>
<button onClick={() => setState(1 + state)}>{state}</button>
<MemoComponent onClick={onClick} />
</main>
);
}
useEffect
It's the alternative for the class component lifecycle methods componentDidMount, componentWillUnmount, componentDidUpdate, etc. You can also use it to create a side effect when dependencies change, i.e. "If some variable changes, do this".
Whenever you have some logic that is executed as reaction to a state change or before a change is about to happen.
useEffect(() => {
// execute when state changed
() => {
// execute before state is changed
}
}, [state]);
OR
useEffect(() => {
// execute when state changed
() => {
// execute before state is changed
}
}, []);
useCallback
On every render, everything that's inside a functional component will run again. If a child component has a dependency on a function from the parent component, the child will re-render every time the parent re-renders even if that function "doesn't change" (the reference changes, but what the function does won't).
It's used for optimization by avoiding unnecessary renders from the child, making the function change the reference only when dependencies change. You should use it when a function is a dependency of a side effect e.g. useEffect.
Whenever you have a function that is depending on certain states. This hook is for performance optimization and prevents a function inside your component to be reassigned unless the depending state is changed.
const myFunction = useCallback(() => {
// execute your logic for myFunction
}, [state]);
Without useCallback, myFunction will be reassigned on every render. Therefore it uses more compute time as it would with useCallback.

useEffect triggered by a context change AFTER component mounts

I'm trying to run a function on a context using useEffect, but after the component mounts. Otherwise the function runs as the context is first loaded in, and gives a bad result. I'd love to nest a useEffect inside another useEffect that runs after load, but I cant. How do I trigger the useEffect to wait until componentDidLoad to run? Do I need to revert my functional component to a class function?
Here is the effect im running
useEffect(() => {
let count = Object.keys(context).filter((x) => context[x] !== "").length;
numChange = count;
}, [context]); <=== run AFTER the original update of the context
To clarify. The context is an object, and I count how many of the objects properties have values. As the context is initially counted (let's say 5 out of the 10 properties already had values), the "number === 5". But what I want to do is count the changes after the initial amount of properties with values in the context when the component mounts. So if n = 5, I want to count any property that gets a value after n.
When your useEffect has a dependency and you want to act it like a componentDidMount, adding a ref check is enough:
const isMounted = useRef(false);
useEffect(() => {
if (!isMounted.current) {
let numChange = Object.keys(context).filter((x) => context[x] !== "")
.length;
setState(numChange);
isMounted.current = true;
}
}, [context]);
See use cases of useEffect for more context and examples.

React hooks update sort of whenever they feel like it

So, I'm using hooks to manage the state of a set of forms, set up like so:
const [fieldValues, setFieldValues] = useState({}) // Nothing, at first
When setting the value, the state doesn't update:
const handleSetValues = values => {
const _fieldValues = {
...fieldValues,
...values
}
setFieldValues(_fieldValues)
console.log(fieldValues) // these won't be updated
setTimeout(() => {
console.log(fieldValues) // after ten seconds, it's still not updated
},10000)
}
If I call the function a second time, it'll have updated, but that's not gonna work for me.
I never saw behaviour like this in class components.
Is it meant to... like, not update? Or just update whenever it feels like it? Really confusing behaviour.
setFieldValues(_fieldValues) is an async call, means you won't able to get the result in the very next line of this.
You can use useEffect hook.
useEffect(() => {
// do your work here
}, [fieldValues]);
It seems from your question that you have background of Class components of React, so useEffect is similar to componentDidMount and componentDidUpdate lifecycle methods.
useEffect calls whenever the state in the dependency array (in your case [fieldValues]) changes and you get the updated value in useEffect body.
You can also perform componentWillUnmount work in useEffect as well.
Have a brief guide.
setFieldValues is an asynchronous function, so logging the value below the statement will not have any effect.
Regarding using setTimeout, the function would capture the current value of props being passed to it and hence that would be the value printed to the console. This is true to any JS function, see the snippet below:
function init(val) {
setTimeout(() => {
console.log(val);
}, 1000);
}
let counterVal = 1;
init(counterVal);
counterVal++;
So how can we print the values when the value changes? The easy mechanism is to use a useEffect:
useEffect(() => {
console.log(fieldValues)
}, [fieldValues]);

Can not update state inside setInterval in react hook

I want to update state every second inside setinterval() but it doesn't work.
I am new to react hook so can not understand why this is happening.
Please take a look at the following code snippet and give me advice.
// State definition
const [gamePlayTime, setGamePlayTime] = React.useState(100);
let targetShowTime = 3;
.........................
// call function
React.useEffect(() => {
gameStart();
}, []);
.............
const gameStart = () => {
gameStartInternal = setInterval(() => {
console.log(gamePlayTime); //always prints 100
if (gamePlayTime % targetShowTime === 0) {
//can not get inside here
const random = (Math.floor(Math.random() * 10000) % wp("70")) + wp("10");
const targetPosition = { x: random, y: hp("90") };
const spinInfoData = getspinArray()[Math.floor(Math.random() * 10) % 4];
NewSpinShow(targetPosition, spinInfoData, spinSpeed);
}
setGamePlayTime(gamePlayTime - 1);
}, 1000);
};
The reason why you did not get updated state is because you called it inside
useEffect(() => {}, []) which is only called just once.
useEffect(() => {}, []) works just like componentDidMount().
When gameStart function is called, gamePlaytime is 100, and inside gameStart, it uses the same value however the timer works and the actual gamePlayTime is changed.
In this case, you should monitor the change of gamePlayTime using useEffect.
...
useEffect(() => {
if (gamePlayTime % targetShowTime === 0) {
const random = (Math.floor(Math.random() * 10000) % wp("70")) + wp("10");
const targetPosition = { x: random, y: hp("90") };
const spinInfoData = getspinArray()[Math.floor(Math.random() * 10) % 4];
NewSpinShow(targetPosition, spinInfoData, spinSpeed);
}
}, [gamePlayTime]);
const gameStart = () => {
gameStartInternal = setInterval(() => {
setGamePlayTime(t => t-1);
}, 1000);
};
...
You're creating a closure because gameStart() "captures" the value of gamePlayTime once when the useEffect hook runs and never updates after that.
To get around this, you must use the functional update pattern of React hook state updating. Instead of passing a new value directly to setGamePlayTime(), you pass it a function and that function receives the old state value when it executes and returns a new value to update with. e.g.:
setGamePlayTime((oldValue) => {
const someNewValue = oldValue + 1;
return someNewValue;
});
Try this (essentially just wrapping the contents of your setInterval function with a functional state update):
const [gamePlayTime, setGamePlayTime] = React.useState(100);
let targetShowTime = 3;
// call function
React.useEffect(() => {
gameStart();
}, []);
const gameStart = () => {
gameStartInternal = setInterval(() => {
setGamePlayTime((oldGamePlayTime) => {
console.log(oldGamePlayTime); // will print previous gamePlayTime value
if (oldGamePlayTime % targetShowTime === 0) {
const random = (Math.floor(Math.random() * 10000) % wp("70")) + wp("10");
const targetPosition = { x: random, y: hp("90") };
const spinInfoData = getspinArray()[Math.floor(Math.random() * 10) % 4];
NewSpinShow(targetPosition, spinInfoData, spinSpeed);
}
return oldGamePlayTime - 1;
});
}, 1000);
};
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
Dan Abramov article explains well how to work with hooks, state, and the setInterval() type of API!
Dan Abramov! Is one of the React maintaining team! So known and I personally love him!
Quick explanation
The problem is the problem of how to access the state with a useEffect() that executes only once (first render)!
Note: For a deep explanation of why the state isn't updated within the useEffect callback and other inside useEffect callbacks. Check the last section about closures and react re-render ...
The short answer is: by the use of refs (useRef)! And another useEffect() that run again when update is necessary! Or at each render!
Let me explain! And check the Dan Abramov solution! And you'll get better the statement above at the end! With a second example that is not about setInterval()!
=>
useEffect() either run once only, or run in each render! or when the dependency update (when provided)!
Accessing state can be possible only through a useEffect() that run and render each relevant time!
Or through setState((state/*here the state*/) => <newStateExpression>)
But if you want to access the state inside useEffect() => re-run is necessary! meaning passing and executing the new callback!
That doesn't work well with setInterval! If you clear it and re-set it each time! the counter get reset! Leading to no execution if the component is re-rendering fast! And make no sense!
If you render only once! The state is not updated! As the first run, run a one callback! And make a closure! The state is fixed! useEffect(() => { <run once, state will stay the same> setInterval(() => { <state fixed as closure of that time> }) }, []).
For all such kind of situation! We need to use useRef! (refs)!
Save to it a callback that holds the state! From a useEffect() that rerenders each time! Or by saving the state value itself in the ref! Depending on the usage!
Dan abramov solution for setInterval (simple and clean)
That's what you are looking for!
useInteval hook (by Dan Abramov)
import React, { useState, useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
Usage
import React, { useState, useEffect, useRef } from 'react';
function Counter() {
let [count, setCount] = useState(0);
useInterval(() => {
// Your custom logic here
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>;
}
We can see how he kept saving the new callback at each re-render! A callback that contains the new state!
To use! it's a clean simple hook! That's a beauty!
Make sure to read Dan article! As he explained and tackled a lot of things!
setState()
Dan Abramov mentioned this in his article!
If we need to set the state! Within a setInteral! One can use simply setState() with the callback version!
useState(() => {
setInterval(() => {
setState((state/*we have the latest state*/) => {
// read and use state
return <newStateExpression>;
})
  }, 1000);
}, []) // run only once
we can even use that! Even when we are not setting a state! Possible! Not good though! We just return the same state value!
setState((state) => {
// Run right away!
// access latest state
return state; // same value (state didn't change)
});
However this will make different react internal code part run (1,2,3), And checks! Which ends by bailing out from re-rendering! Just fun to know!
We use this only when we are updating the state! If not! Then we need to use refs!
Another example: useState() with getter version
To showcase the how to work with refs and state access! Let's go for another example! Here is another pattern! Passing state in callbacks!
import React from 'react';
function useState(defaultVal) {
// getting the state
const [state, setState] = React.useState(defaultValue);
// state holding ref
const stateRef = React.useRef();
stateRef.current = state; // setting directly here!
// Because we need to return things at the end of the hook execution
// not an effect
// getter
function getState() {
// returning the ref (not the state directly)
// So getter can be used any where!
return stateRef.current;
}
return [state, setState, getState];
}
The example is of the same category! But here no effect!
However, we can use the above hook to access the state in the hook simply as below!
const [state, useState, getState] = useState(); // Our version! not react
// ref is already updated by this call
React.useEffect(() => {
setInteval(() => {
const state = getState();
// do what you want with the state!
// it works because of the ref! Get State to return a value to the same ref!
// which is already updated
}, 1000)
}, []); // running only once
For setInterval()! The good solution is Dan Abramov hook! Making a strong custom hook for a thing is a cool thing to do! This second example is more to showcase the usage and importance of refs, in such state access need or problem!
It's simple! We can always make a custom hook! Use refs! And update the state in ref! Or a callback that holds the new state! Depending on usage! We set the ref on the render (directly in the custom hook [the block execute in render()])! Or in a useEffect()! That re-run at each render or depending on the dependencies!
Note about useEffect() and refs setting
To note about useEffect()
useEffect => useEffect runs asynchronously, and after a render is painted on the screen.
You cause a render somehow (change state, or the parent re-renders)
React renders your component (calls it)
The screen is visually updated
THEN useEffect runs
Very important of a thing! useEffect() runs after render() finishes and the screen is visually updated! It runs last! You should be aware!
Generally, however! Effects should be run on useEffect()! And so any custom hook will be ok! As its useEffect() will run after painting and before any other in render useEffect()! If not! As like needing to run something in the render directly! Then you should just pass the state directly! Some people may pass a callback! Imagine some Logic component! And a getState callback was passed to it! Not a good practice!
And if you do something somewhere of some such sense! And talking about ref! Make sure the refs are updated right! And before!
But generally, you'll never have a problem! If you do then it's a smell! the way you are trying to go with is high probably not the right good way!
Closure and more. Why the state variables are not having the latest value?
The why you can't simply access the state value directly boils down to the closure notion. Render function at every re-render go totally with a new call. Each call has its closures. And at every re-render time, the useEffect callback is a new anonymous function. With its new scope of value.
And here the dependency array matter. To access the closure of this call. And so the recent state of this call. You have to make useEffect use the new callback. If dependencies change then that would happen. If not that wouldn't.
And if you do [] then useEffect() would run the first time only. every new call after the first render. Would always have useEffect get a new anonymous function. But none of them is effective or used (run).
The same concept applies to useCallback and many other hooks. And all of that is a result of closures.
callbacks inside the useEffect callback
ex: event listners, setInterval, setTimeout, some(() => {}), api.run(() => {})
Now even if you update the useEffect callback through dependency change. And let's say you did some event listener or setInterval call. But you did it conditionally so if already running no more setting it. The setInterval callback or event listener callback wouldn't get access to the recent state value. Why? you already guessed. They were created in the first run, first call space and all the state values are closure passed down at that **time**. And in the later updates and calls. It's a new render function call. And a whole different useEffect callback as well. Sure it's the same function code. But not same functions at all. The initial callback of the initial run on that render function call. Is a function that was created back then. And then the event listener callback or the setInterval callback was created back then. And they are still in memory and referred to. The event listener part of the event listener API objects instances, and the object that made the registration and the setInterval part of the node runtime. They had the state closure having the value of the time. And never get to see or know about any other re-render call. **Unless somehow you. inject something within. That still referencesorcan access the latest value(references or globals). And those values come from the hooks (useState) and their internal machinery. All it would have is theclosures of the time of the creation.**
Funny metaphor
Most people falling into this trap. Look at the code and saying hey why when the state updates it's not getting the newer value. And the answer is. The state value is something coming from useState, it's not a global variable. And even though you are looking at the same code. The first call and later calls are all different spaces (beings). The only thing making it possible for us to work with functions in this sense is the hooks. And how they store the state and bring it back.
And a good metaphor would be: to get to an office and do some contract and deal. And then later come back and enter the same office, but wait not really the same office rather one that looks the same. But a new office took its place (moved out, moved in, same activity). And you wonder why they didn't recognize you. Yup a bit of an off of a metaphor. But still good a bit.
On the whole I hope that gave you a good sense!
You shouldn't use setInterval with hooks. Take a look at what Dan Abramov, one of the maintainers of React.js, said regarding an alternative on his blog: https://overreacted.io/making-setinterval-declarative-with-react-hooks/

Resources