I'm trying to create a small component that will fade between it's children when one of its methods is called. I've been following this code, but so it supports any number of children. So far, I have this:
export const Fader = React.forwardRef<FaderProps, Props>((props, ref) => {
const children = React.Children.toArray(props.children);
const [currentChild, setCurrentChild] = useState({
child: props.startIndex || 0,
direction: 1,
});
let nextChild = 0;
const fadeNext = (): void => {
queueNextFade(); //Queues the next fade which fades in the next child after the current child has faded out
setCurrentChild({
child: currentChild.child,
direction: +!currentChild.direction,
});
nextChild = currentChild.child + 1;
}
const fadePrev = (): void => {
}
const fadeTo = (index: number): void => {
}
const queueNextFade = (): void => {
setTimeout(() => {
setCurrentChild({
child: nextChild,
direction: +!currentChild.direction,
});
}, props.fadeTime || 500)
}
useImperativeHandle(ref, () => ({ fadeNext, fadePrev, fadeTo }));
return (
<div>
{
React.Children.map(children, (child, i) => (
<div key={i}
style={{
opacity: i === currentChild.child ? currentChild.direction : "0",
transition: `opacity ${props.fadeTime || 500}ms ease-in`,
}}
>
{child}
</div>
))
}
</div>
)
});
Logic wise it does work, but what actually happens is that the first child fades out, but the next child doesn't fade in. If faded again, the second child fades in then fades out, and the next child fades in. (View in sandbox)
For a while I was confused as to why that was happening because I'm using the same logic as the other library. I did some research on seeing if I could make useState
be instant and I came across this post and I quote from it:
Even if you add a
setTimeout
the function, though the timeout will run after some time by which the re-render would have happened, thesetTimeout
will still use the value from its previous closure and not the updated one.
which I realised is what's happening in my situation. I start the setTimeout
in which currentChild.direction
is 1
. Then a state change happens and the direction changes to 0
. A long time later the setTimeout
finishes but instead of changing the direction from 0
to 1
, it changes from 1
to 0
because it kept it original value from when it was first called, hence why the second child doesn't fade in and just stays invisible.
I could just change it to:
let currentChild = {...}
and the have a "blank" useState
to act as a forceUpdate
but I know force updates are against React's nature and there's probably a better way to do this anyway.
If anyone could help out, I would appreaciate it