Abnormal behaviour of React useEffect Hook - reactjs

I am facing an abnormal output on the browser from React while using useEffect hook.
I would request you to please have a look at the code. You can copy and paste the code on any online IDE that supports React to visualize the behavior on the browser.
I want the counter to increment after every 1 second. But with the code it stucks after 10.
import { useState, useEffect } from "react";
function App() {
const initialState = 0;
const [count, setCount] = useState(initialState);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 1000);
// return () => {
// clearInterval(interval);
// };
}, [count]);
return (
<div className="App">
<h1>{count}</h1>
</div>
);
}
export default App;
I want to know the reason for that. Why is it happening?
But when I do cleanup with useEffect to do componentWillUnmoint() it behaves normal and renders the counter every second properly. I have intentionally comment cleanup part of code useEffect.

You are adding an interval on every render, soon enough, your thread will be overloaded with intervals.
I guess you wanted to run a single interval, its done by removing the closure on count by passing a function to state setter ("functional update"):
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return (
<div className="App">
<h1>{count}</h1>
</div>
);
}
export default App;

Related

How do I trigger React UseEffect on only the first render?

In am using React and trying to trigger a function only once, when the page initially loads. Currently, the below code triggers the console message twice at page load.
import { useState, useEffect, useRef } from "react";
export default function TestRef(){
const [inputValue, setInputValue] = useState("");
const count = useRef(null);
const myFunc = () => {
console.log('Function Triggered');
}
useEffect(() => {
if(!count.current){
count.current = 1;
myFunc();
}
return () => { count.current = null; }
}, []);
return (
<>
<p>Page content</p>
</>
);
}
I have read up on how React 18 intentionally double-renders elements, which is my reason for using useRef and for returning the cleanup function in the useEffect hook, but it still doesn't seem to work.
hi please make sure you didn't invoke TestRef componenet twice in your page!
for debug and find rerenders you can use react profiler extention on chrome then remove extra rerender by using momo and useMemo and useCallback
Finally got it to work by doing this. This appears to only run myFunc() once, on the initial rendering of the component.
import { useState, useEffect, useRef } from "react";
export default function TestRef(){
const [inputValue, setInputValue] = useState("");
const count = useRef(null);
const myFunc = () => {
console.log('Function Triggered');
}
useEffect(() => {
if(count.current == null){
myFunc();
}
return () => { count.current = 1; }
}, []);
return (
<>
<p>Page content</p>
</>
);
}

Update state in setInterval not working as expected

I'm new to react, today I encounter a very strange case, what I want todo is to get the data from server side every 5 seconds, but it doesn't work as expected, the problem can be simplied to this:
npx create-react-app my-app
cd my-app
edit the App.js
import { useState } from "react";
function App() {
const [count, setCount] = useState(0)
const increment = () => {
// simulate the querying, for example, checking the status of server
console.log('Current count value:', count)
setCount(count + 1)
}
setInterval(increment, 1000)
return (
<span>Count: {count}</span>
);
}
export default App;
I want to update the state count every 1s, and I think the output in console would be
Current count value: 0
Current count value: 1
Current count value: 2
...
However, the output is very strange, click the link to see the output
(I cant' insert image in the content)
chrome console output
Thanks
You need to start interval in useEffect and clear when component unmount
import React, { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0)
useEffect(() => {
const increment = () => {
// simulate the querying, for example, checking the status of server
console.log('Current count value:', count)
setCount(prev => prev + 1) // You can't use count state because it's will trigger useEffect and cause infinite loop. You need to use callback to update from previous value
}
const intervalId = setInterval(increment, 1000) // setInterval return interval id
return () => {
// Clear interval using intervalId
// This function run when component unmount
clearInterval(intervalId)
}
}, [])
return (
<span>Count: {count}</span>
);
}
export default App;
You can use setTimeout instead setinterval-is-moderately-evil
const DURATION = 1000;
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setTimeout(() => {
setCount(count + 1);
}, DURATION);
});
You can read more of how to use setInterval in react here making-setinterval-declarative-with-react-hooks
You need to use useEffect hook to keep track of changes in the component's states.
trying to invoke a function inside the body of your component will make that function to re-invoke everytime any DOM is updated. The best practice to initiate your component is to put it inside a useEffect hook without dependencies.
useEffect(() => {
setInterval(increment, 1000)
}, []);
Now if you want to keep track of a state when it's updated you can use anotther useEffect and add the state in dependency array. Like:
useEffect(() => {
console.log('incremented', count)
}, [count]);
Here is an edited version of your code:
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0)
// a function that changes count state
const increment = () => {
setCount(prevState => prevState + 1)
}
// when component is mounted
useEffect(() => {
setInterval(increment, 1000)
}, []);
// when count state changes
useEffect(() => {
console.log('incremented', count)
}, [count]);
return (
<span>Count: {count}</span>
);
}
export default App;

useEffect getting triggered repeatedly even after applying [] brackets

I was learning react and came across the concept of useEffect. So I was trying useEffects with resize event listeners and when doing so even after applying the square bracket which should be used only to run the useEffect once, I am repeatedly getting the updated size of the screen when trying the change the browser size.
below is the code, could some one please let me know if I have some code issue or is this how it is supposed to work?
import React, { useState, useEffect } from "react";
// cleanup function
// second argument
const UseEffectCleanup = () => {
const [size, setsize] = useState(window.innerWidth);
const checkSize = () => {
console.log("check size");
setsize(window.innerWidth);
};
useEffect(() => {
console.log("use effect");
window.addEventListener("resize", checkSize);
return () => {
console.log("Cleanup");
window.removeEventListener("resize", checkSize);
};
}, []);
return (
<>
<h2>Windows Width</h2>
<h2>{size}</h2>
</>
);
};
export default UseEffectCleanup;

How to properly add React window.addEventListener to be able to change useState via it

I have tried my combinations of how to add the event listener and I just don't understand how to achieve the result now.
My goal is to bind "keydown" events to some actions that will further change component's state.
This approach somehow works, and by pressing left arrow on keyboard the count decreases.
But for some reason, the more you press the left arrow the more there are duplicated events in console, up until the point when it gets flooded.
import { useState, useEffect, useCallback } from "react";
export default function App() {
const [count, setCount] = useState(0);
const countDown = useCallback(() => {
setCount(count - 1);
console.log(count);
}, [count]);
useEffect(() => {
window.addEventListener("keydown", (event) => {
if (event.code === "ArrowLeft") {
countDown();
}
});
}, [countDown]);
return (
<div className="App">
<div>{count}</div>
</div>
);
}
Can someone please help with this?
Your console gets flooded because you are creating new listeners in every effect execution...
Make sure you remove the listener in the effect cleanup function too.
const keyDownListener = useCallback((event) => {
if (event.code === "ArrowLeft") {
countDown();
}
});
useEffect(() => {
window.addEventListener("keydown", keyDownListener );
return () => {
window.removeEventListener("keydown", keyDownListener );
}
}, [countDown]);

useEffect localStorage loop

I'm trying to storage a single state and I cannot do that apparently because of a infinite loop. Could you help me?
import React, { useState, useEffect } from "react";
const App = () => {
const [rows, setRows] = useState("Inicial State");
function init() {
const data = localStorage.getItem("my-list");
if (data) {
setRows(JSON.parse(data));
}
localStorage.setItem("my-list", JSON.stringify(rows));
}
useEffect(() => {
init();
});
return (
<div>
<button onClick={() => setRows("Loaded state!")}>Load!</button>
<div>{rows}</div>
</div>
);
};
export default App;
You call init() every time component re-render. Document how to use useEffect here: https://reactjs.org/docs/hooks-effect.html. You should only call one time like componentDidMount in class component by:
useEffect(() => {
init();
}, []);
useEffect(() => {
localStorage.setItem("my-list", JSON.stringify(rows));
}, [rows]);
If you are using useEffect for initialisation, it needs to have an empty dependency array to make sure it only runs onthe first render, not on every render:
useEffect(() => init(), []);

Resources