I have a functional component and I want to change State inside a setTimeout
, but I don't know why it causes the component to be in an infinite loop!
Here is my Functional Component:
import { useState } from "react";
function Card() {
const [booksState, setBooks] = useState({
books: [
{ name: "Harry", id: 1 },
{ name: "Potter", id: 2 },
{ name: "John", id: 3 },
],
});
console.log("test");
setTimeout(() => {
let newBooks = [
{ name: "test1", id: 4 },
{ name: "test2", id: 5 },
{ name: "test3", id: 6 },
];
setBooks({
books: [...booksState.books, ...newBooks],
});
}, 2000);
return <div>TEST</div>;
}
export default Card;
The console log:
CodePudding user response:
Set Timeout runs every time the component renders. This means every time the state changes, it starts a new timer. Assuming you only want to run the timer when the component first renders, you should use the useEffect
hook as follows:
import { useState } from "react";
function Card() {
const [booksState, setBooks] = useState({
books: [
{ name: "Harry", id: 1 },
{ name: "Potter", id: 2 },
{ name: "John", id: 3 },
],
});
console.log("test");
useEffect(() => {
setTimeout(() => {
let newBooks = [
{ name: "test1", id: 4 },
{ name: "test2", id: 5 },
{ name: "test3", id: 6 },
];
setBooks({
books: [...booksState.books, ...newBooks],
});
}, 2000)
}, []);
return <div>TEST</div>;
}
export default Card;
CodePudding user response:
You need to call setTimeout
inside a useEffect
to avoid infinite calling of setTimeout
.
In the current implementation, setTimeout gets called again after the first timeout completes (due to rerendering) which causes it to exponentially accumulate the timeout calls.
useEffect(() => {
setTimeout(() => {
let newBooks = [
{ name: "test1", id: 4 },
{ name: "test2", id: 5 },
{ name: "test3", id: 6 }
];
setBooks({
books: [...booksState.books, ...newBooks]
});
}, 2000);
}, []);
CodePudding user response:
In your current implementation setTimeout
runs every time the component renders means on every state update so because of that it's getting called again and again. So, you need to call setTimeout
inside of useEffect
. And don't forget to clearTimeout
in the useEffect
's cleanup function.
useEffect(() => {
const myTimeout = setTimeout(() => {
let newBooks = [
{ name: "test1", id: 4 },
{ name: "test2", id: 5 },
{ name: "test3", id: 6 },
];
setBooks({
books: [...booksState.books, ...newBooks],
});
}, 2000)
return () => {
clearTimeout(myTimeout)
}
}, []);