I am working on building a simple React slider which will expose internal methods up to its parent via a ref and I am having trouble with what I suspect to be a stale closure, but I can't fully understand what is actually happening. Hoping someone can help me understand here.
Here is a simplified version of the code that I want to work:
const Slider = forwardRef((props, ref) => {
const sliderRef = useRef();
const [slides, dispatchSlides] = useReducer(reducer, []);
sliderRef.current = {
countSlides: () => {
return slides.length
},
};
useImperativeHandle(ref, () => sliderRef.current);
return null;
After this component mounts, its children will render and fill up the slides reducer with information on their positioning and visibility using IntersectionObserver. This part works, so I have kept it out of this example for simplicity. For our sake, just assume that slides is immediately populated with objects after mount, and that a user will manually call countObjects from the parent component much later after slides has been populated.
In the parent component, if I execute countSlides from the ref, I will always see slides.length === 0, no matter how many slides are actually present. I assume this is because the original countSlides method is a stale closure.
Now, what I don't fully understand, is that if I adjust this line:
useImperativeHandle(ref, () => sliderRef.current);
to this:
useImperativeHandle(ref, () => () => {
countSlides: () => sliderRef.current.countSlides()
});
the stale closure is fixed and everything works as intended. But this is duplicative code and I'm just not sure what is even different between the two cases. I do not want to repeat myself redefining many methods within the useImperativeHandle hook, but much more importantly, I want to understand what the difference is between the two examples above.
Thank you!
EDIT Adding full example:
https://codesandbox.io/s/ssr-slider-6ywf9
As you commented that the problem arose only when writing like onClick={ slider?.current?.prev } instead of onClick={() => { slider?.current?.prev() }}
I have tried with my sandbox that I provided and got the same problem.
There're a few things here:
So, if we write like onClick={slider?.current?.prev}, what happens is:
But, if we write like onClick={() => { slider?.current?.prev() }}, what happens is:
slider?.current?.prevIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With