useEffect infinite loop occurs even when second dependency exists - reactjs

const [hanziList, sethanziList] = useState([]);
const [whetherFinish, setWhetherFinish] = useState()
const inspectCheckboxList = () => {
var i = 0;
while (i < hanziList.length) {
if (hanziList[i].memorize === 0) {
setWhetherFinish("notFinish")
break;
}
if (hanziList[i].memorize === 1) {
i = i + 1;
}
if (i === hanziList.length) {
setWhetherFinish("finish")
}
console.log(whetherFinish)
}
}
useEffect(() => { inspectCheckboxList(); }, [hanziList]);
hanziList is an object which came from MySQL, it is a list of Chinese voca. and on List, if I check checkbox then the value of memorize turns into 1. So, 0 means not memorized, 1 means memorized.
If I check all of the checkboxes, it means that I have memorized all of the voca on the list, so the className of a button changes from "notFinish" into "finish".
And to check whether it is "finish", I made a function inspectCheckboxList. Every time the value of memorize changes, by useEffect render the function.
I found that putting state inside useEffect is a way to solve the problem, but it is not possible for me.
I'm not sure why the infinite loop occurs, because by useEffect, the function only renders when the website first laods, and the value of memorize (in this case, hanziList`) changes.
I think this is because inside function concludes hanziList, and the second dependency is also hanziList.
Why does the infinite loop occur, even when I set second dependency, and how to solve this problem?

Related

How to prevent a value from changing during rerenders?

I am making a list of lists of checkboxes. I have a ref bodyOriginal that I use to track the values I received from the database to know if they were altered or not and to activate or not the save button, and this ref is only altered on the function that makes the database request, and that function is only executed once (I checked). When I press to check a box I use this function
const handleCheckboxGroup = useCallback((event, index, optionIndex) => {
const original = JSON.stringify(bodyOriginal.current[index]);
setBody(prev =>
prev.map((item, indexPrev) => {
if (indexPrev === index) {
item.options[optionIndex].checked = event.target.checked;
item.active = JSON.stringify(item) !== original; //<-
}
return item;
})
);
}, []);
When I do it the first time it works as intended, but when I do it again the ref bodyOriginal changes to match what the last body was, therefore making the comparison (at the "<-") always different.
If I skip the declaration of a variable and make the direct comparison:
JSON.stringify(item) !== JSON.stringify(bodyOriginal.current[index])
It will always be false, because somewhere between the declaration of "original" and the comparison the variable will change to match body (despite not being called anywhere else). I was told this might be a rerender problem but I tried useCallback and useMemo and none of it seemed to stop it. How can I stop it from being altered?

useEffect dependency array understanding

Hello I am learning about the useEffect hook in react, and I need some clarification. It is my understand that when we provide an empty dependency array that the code inside of the useEffect hook will only run once during the application's initial mount. I need some helping understand why the code below runs every time I refresh the page even though I provided an empty dependency array?
Thank you
const [numberOfVistors, setnumberOfVistors] = useState(() => {
localStorage.getItem("numberOfVistorsKey")
});
useEffect (() => {
let vistorCount = localStorage.getItem("numberOfVistorsKey")
if (vistorCount > 0)
{
vistorCount = Number(vistorCount) + 1
localStorage.setItem("numberOfVistorsKey", vistorCount)
setnumberOfVistors(vistorCount)
}
else
{
vistorCount = 1
setnumberOfVistors(vistorCount)
localStorage.setItem("numberOfVistorsKey", vistorCount)
}
}, [])
The context of an application does not persist across multiple pageloads or refreshes except through the few ways that allow for persistent data - such as local storage, cookies, and an API with a server somewhere. For the same reason, doing
let num = 5;
button.onclick = () => { num = 10; }
results in num still being 5 after you click and reload the page - because reloading the page starts the script from the very beginning again, on the new page.
If you want to keep track of whether that particular section of code has ever run before, use a flag in storage, eg:
useEffect(() => {
if (localStorage.hasVisitedBefore) return;
localStorage.hasVisitedBefore = 'yes';
// rest of effect hook
, []);
I need some helping understand why the code below runs every time I refresh the page even though I provided an empty dependency array?
Every time you refresh the page, the React component mounts for the first time.
useEffect(..., []) was supplied with an empty array as the dependencies argument. When configured in such a way, the useEffect() executes the callback just once, after initial mounting.
Try going through it !!
Whenever you refresh the page, React component will be re-rendered. Therefore useEffect() is called everytime.
In order to avoid it, you have to do like this.
const [numberOfVistors, setnumberOfVistors] = useState(() => {
localStorage.getItem("numberOfVistorsKey")
});
useEffect (() => {
let vistorCount = localStorage.getItem("numberOfVistorsKey")
if (vistorCount > 0)
{
vistorCount = Number(vistorCount) + 1
localStorage.setItem("numberOfVistorsKey", vistorCount)
setnumberOfVistors(vistorCount)
}
else
{
vistorCount = 1
setnumberOfVistors(vistorCount)
localStorage.setItem("numberOfVistorsKey", vistorCount)
}
}, [numberOfVistors])
This code will render your component whenever numberOfVistors are changed.

React JS wait for useeffect to finish all renders

I have the next situation in my react application. I have a state that is set from another component: const [test, setTest] = useState();, when this state is set it create some renders like:
first render test = first value
second render test = first value
last render test = the expected value.
What i want to do is to read only the last value of test inside useEffect hook like:
useEffect(() => {
// here i need to avoid the previous values of test and to display only the last
console.log(test);
}, [test]);
Question: How inside useEffect to avoid the previous values of test and to display only the last?
You can initialise a counter variable outside the uesEffect, and increment it everytime the useEffect runs. Once you the desired condition is met(eg, if(counter===2)), then run console.log.
so
let counter = 0;
useEffect(()=>{
counter++;
if(counter==2){
console.log(test)
}
},[test])
I don't think the best solution is to wait for the 3rd call of the useEffect. It already means that the first 2 renders are useless, not too optimized but ok...
On the other hand, why not just test the value where you need it?
if(!!test && test === expectedValue)
or in jsx / tsx :
{!!test && test === expectedValue && <></>}

I am confused as to how my dependencies change every render?

// We are only running this at initial render and anytime
// yelpResults gets updated (only once per food and location submit)
useEffect(() => {
// Creating a temp array so our restaurantIndexes is immutable
let tempArray = [];
// Concatenating the value of each index into our state
Object.keys(yelpResults).map((index) => tempArray.push(index));
// Saving the results of our restaurant indexes
setRestaurantIndexes(tempArray);
}, [yelpResults, restaurantIndexes]);
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
I think what you are doing wrong is you are re-rendering things when one of these values changes [yelpResults, restaurantIndexes] because useEffect renders again when value changes.So a better solution would be to put an if statement which will check if the value has changed or not.
const [restaurantIndexes, setRestaurantIndexes] = useState("")
useEffect(() => {
// Creating a temp array so our restaurantIndexes is immutable
let tempArray = [];
// Concatenating the value of each index into our state
Object.keys(yelpResults).map((index) => tempArray.push(index));
console.log(tempArray);
//Check if tempArray has changed
if(restaurantIndexes !== tempArray) return
// Set the RestaurantIndex state
setRestaurantIndexes(tempArray);
}, [yelpResults,restaurantIndexes]);

How does React Hooks useCallback "freezes" the closure?

I'd like to know how does React "freezes" the closure while using the useCallback hook (and with others as well), and then only updates variables used inside the hook when you pass them into the inputs parameter.
I understand that the "freeze" may not be very clear, so I created a REPL.it that shows what I mean: https://repl.it/repls/RudeMintcreamShoutcast. Once you open the code, open your web browser console and start clicking on the count button.
How come the value outside compared to the one inside, for the same variable, is different, if they're under the same closure and referencing the same thing? I'm not familiar with React codebase and so I suppose I'm missing an under the hood implementation detail here, but I tried to think how that could work for several minutes but couldn't come up with a good understanding on how React is achieving that.
The first time the component is rendered, the useCallback hook will take the function that is passed as its argument and stores it behind the scenes. When you call the callback, it will call your function. So far, so good.
The second time that the component is rendered, the useCallback hook will check the dependencies you passed in. If they have not changed, the function you pass in is totally ignored! When you call the callback, it will call the function you passed in on the first render, which still references the same values from that point in time. This has nothing to do with the values you passed in as dependencies - it's just normal JavaScript closures!
When the dependencies change, the useCallback hook will take the function you pass in and replace the function it has stored. When you call the callback, it will call the new version of the function.
So in other words, there's no "frozen"/conditionally updated variables - it's just storing a function and then re-using it, nothing more fancy than that :)
EDIT: Here's an example that demonstrates what's going on in pure JavaScript:
// React has some component-local storage that it tracks behind the scenes.
// useState and useCallback both hook into this.
//
// Imagine there's a 'storage' variable for every instance of your
// component.
const storage = {};
function useState(init) {
if (storage.data === undefined) {
storage.data = init;
}
return [storage.data, (value) => storage.data = value];
}
function useCallback(fn) {
// The real version would check dependencies here, but since our callback
// should only update on the first render, this will suffice.
if (storage.callback === undefined) {
storage.callback = fn;
}
return storage.callback;
}
function MyComponent() {
const [data, setData] = useState(0);
const callback = useCallback(() => data);
// Rather than outputting DOM, we'll just log.
console.log("data:", data);
console.log("callback:", callback());
return {
increase: () => setData(data + 1)
}
}
let instance = MyComponent(); // Let's 'render' our component...
instance.increase(); // This would trigger a re-render, so we call our component again...
instance = MyComponent();
instance.increase(); // and again...
instance = MyComponent();
I came here with a similar, rather vague uncertainty about the way useCallback works, its interaction with closures, and the way they are "frozen" by it. I'd like to expand a bit on the accepted answer by proposing to look at the following setup, which shows the working of useCallback (the important aspect is to ignore the linter's warning, for pedagogical reasons):
function App() {
const [a, setA] = useState(0)
const incrementWithUseCallback = useCallback(() => {
// As it closes on the first time `App` is called, the closure is "frozen" in an environment where a=0, forever
console.log(a)
setA(a + 1)
}, []) // but.. the linter should complain about this, saying that `a` should be included!
const incrementWithoutUseCallback = () => {
// This will see every value of a, as a new closure is created at every render (i.e. every time `App` is called)
console.log(a)
setA(a + 1)
}
return (
<div>
<button onClick={incrementWithUseCallback}>Increment with useCallback</button>
<button onClick={incrementWithoutUseCallback}>Increment without useCallback</button>
</div>
)
}
So we clearly see that useCallback effectively "freezes" its closure at a certain moment in time, which is a concept that must be understood clearly, in order to avoid confusing problems, which are sometimes also referred as "stale closures". This article probably does a better job of explaining it than me: https://tkdodo.eu/blog/hooks-dependencies-and-stale-closures
Here's a slightly another view on example code provided by Joe Clay, which emphasizes closure context in which callback is called.
//internal store for states and callbacks
let Store = { data: "+", callback: null };
function functionalComponent(uniqClosureName) {
const data = Store.data;//save value from store to closure variable
const callback = Store.callback = Store.callback || (() => {
console.log('Callback executed in ' + uniqClosureName + ' context');
return data;
});
console.log("data:", data, "callback():", callback());
return {
increase: () => Store.data = Store.data + "+"
}
}
let instance = functionalComponent('First render');
instance.increase();
instance = functionalComponent('Second render');
instance.increase();
instance = functionalComponent('Third render');
As you see, callback without dependencies will be always executed in the closure where it was memorized by useCallback, thus 'freezing' closure.
It happens because when function for callback is created, it is created only once, during first 'render'. Later this function is re-used, and use value of data which was recorded from Store.data during first call.
In the next example you can see the closure 'freezing' logic "in essence".
let globalX = 1;
const f = (() => {
let localX = globalX; return () => console.log(localX); }
)();
globalX = 2;//does not affect localX, it is already saved in the closure
f();//prints 1

Resources