A react-spring (version 9) spring whose 'to' value is an async funtion goes through its animation cycle every single rerender. If 'to' is a plain object, the animation triggers only initially, as expected.
Consider this component:
const Component = () => {
// let's trigger some rerenders
const [state, setState] = useState(false);
useEffect(() => {
setInterval(() => {
setState(x => !x);
}, 1000);
}, []);
// a spring with an async func provided to 'to'
const props = useSpring({
to: async (next, cancel) => {
await next({opacity: 1, color: '#ffaaee'})
await next({opacity: 0, color: 'rgb(14,26,19)'})
},
from: {opacity: 0, color: 'red'}
});
return <animated.div style={props}>I will fade in and out</animated.div>
};
The text will keep flashing forever.
I believe this is not the intended behaviour. Is this a bug, or I'm doing something wrong?
I think the intended behaviour is to show the current state of the to property of the useSpring. When it is constant then it will always show the same state at each render. But you can change the to property of the usespring. For example:
const ComponentColor = () => {
const [color, setColor] = React.useState("red");
const props = useSpring({
to: { color },
from: { color: "red" }
});
return (
<>
<animated.h2 style={props}>This color will change</animated.h2>
<button onClick={() => setColor("blue")}>blue</button>
<button onClick={() => setColor("green")}>green</button>
<button onClick={() => setColor("orange")}>orange</button>
</>
);
};
In this case the color of the text will change to the color you pressed. I think your example is in line with this one. At each render it will show the current state of the to property which is a sequence. So I think it is the intended behaviour.
If you want that useState animate only on first render. Then you can refactor the animation part to a new component and make sure, that it will only render for the first time. For example if you use React.memo it will rerender your function component only if one of its properties change. In this example there is no property so it will render only for the very first time.
const Component = () => {
// let's trigger some rerenders
const [state, setState] = React.useState(false);
React.useEffect(() => {
setInterval(() => {
setState(x => !x);
}, 2000);
}, []);
return <FirstTimeAnimate />;
};
const FirstTimeAnimate = React.memo(() => {
const props = useSpring({
to: async (next, cancel) => {
await next({ opacity: 0.25, color: "#black" });
await next({ opacity: 1, color: "blue" });
},
from: { opacity: 0, color: "red" }
});
return <animated.h2 style={props}>I will fade in and out</animated.h2>;
});