Reputation: 73
I update state inside useEffect and expect webpage to rerender on every state update but it seems like it only updates once - the last value from for loop. And despite me setting timeout the update and rerender happens instantly. What am I doing wrong?
const [definition, setDefinition] = useState('')
useEffect(() => {
if (startAnimationFinished) return
if (!pathIsActive) return
let newDef
for (let i = 0; i < 150; i++) {
newDef = `M ${i} ${i} L ${i} ${i}`
setTimeout(() => {
setDefinition(newDef)
}, 1000)
}
console.log(newDef);
startAnimationFinished = true
}, [pathIsActive])
return (
<>
<svg
className={'path' + (pathIsActive ? ' active' : '')}
xmlns="http://www.w3.org/2000/svg"
style={{ border: '1px solid blue' }}
viewBox={`${-width / 4} ${-width / 4} ${width} ${width}`}
{...props}
>
<path fill='none' stroke="white" width='1'
style={{ filter: "drop-shadow(0px 0px 5px white)" }}
d={definition}
/>
</svg>
</>
Upvotes: 1
Views: 144
Reputation: 850
setTimeout
s to run at the same timesetTimeout
s will see the same value of the newDef
variable (which will be the last assigned value).If you want to update the state in 1 second intervals you can do something like this:
const updateEverySecond = async () => {
let i = 0;
while (i++ < 150) {
const newDef = `M ${i} ${i} L ${i} ${i}`;
setDefinition(newDef);
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
useEffect(() => {
updateEverySecond();
}, []);
Upvotes: 1
Reputation: 609
If you want start animation per sec use setInterval
instead of setTimeOut
import React, { useState } from "react";
export default function App() {
const [definition, setDefinition] = React.useState("");
const [secs, setSecs] = useState(0);
const [pathIsActive, setActivePath] = useState(false);
React.useEffect(() => {
let timerId;
let startAnimationFinished = false;
const startAnim = () => {
if (!startAnimationFinished && !pathIsActive) {
let newDef;
timerId = setInterval(() => {
newDef = `M ${secs} ${secs} L ${secs} ${secs}`;
setDefinition(newDef);
setSecs((pre) => pre + 1);
}, 1000);
}
};
const stopAnim = () => {
clearInterval(timerId);
startAnimationFinished = true;
};
if (secs <= 150) {
startAnim();
} else {
stopAnim();
}
return () => {
clearInterval(timerId);
startAnimationFinished = false;
};
}, [secs, pathIsActive]);
return (
<>
<svg
className={"path" + (pathIsActive ? " active" : "")}
xmlns="http://www.w3.org/2000/svg"
style={{ border: "1px solid blue" }}
viewBox={`${-width / 4} ${-width / 4} ${width} ${width}`}
{...props}
>
<path
fill="none"
stroke="white"
width="1"
style={{ filter: "drop-shadow(0px 0px 5px white)" }}
d={definition}
/>
</svg>
</>
);
}
Upvotes: 1
Reputation: 16189
React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
consider updating multiple hooks
in your useEffect
, this will not be good behavior if the component
will be rendered for every update. so once useEffect
ends the component
re render.
here if you want to store all the values without loosing any, because here you are overwritting the value of definition
in every itteration you can declare definition
as array
const [definition, setDefinition] = useState([]);
then update it like this :
let newDef = [];
for (let i = 0; i < 150; i++) {
newDef.push(`M ${i} ${i} L ${i} ${i}`)
}
setDefinition(newDef)
now you have the list of definitions you can display them as you want
Upvotes: 1