why do we need to put the onViewableItemsChanged prop inside a useRef hook in react native

Viewed 95
      const _onViewableItemsChanged = useRef(({ viewableItems }) => {
                setCurrentPageIndex(viewableItems[0].index ?? 0);
       });

So, I found this code online that gets the index of the currently viewed page inside a FlatLisk but I'm having a hard time understanding why we should put it inside the useRef.

1 Answers

The code that is used is actually:

const onViewableItemsChanged = useRef(({ viewableItems }) => {
    setCurrentPageIndex(viewableItems[0].index ?? 0);
}).current;

which should make the intent of this approach clearer. The callback passed to useRef is called only once, when the component mounts, and the object returned is stable across mounts. But with this code, the value inside the object is extracted immediately with the .current. Essentially, what this does is create a stable instance variable for a given mount of this component.

Compare with, for example:

const onViewableItemsChanged = ({ viewableItems }) => {
    setCurrentPageIndex(viewableItems[0].index ?? 0);
};

This is pretty similar to the approach with useRef, except that the function is created anew each time the component re-renders. There are no stale closures to worry about here, because the only outside variable referenced inside the function is setCurrentPageIndex, and setCurrentPageIndex is stable across renders.

Whether a function is re-created or not generally only matters in relation to child components that use the function. When you pass down a new function as a prop to a child component, a decently designed child component will detect that change, remove whatever functionality was using the old function, and reattach the functionality using the new function. For example, if the child component has:

const Child = ({ fn }) => (
  <button onClick={fn}>click</button>
);

Then whenever the fn prop passed down by the parent changes, the child must remove the previous click listener from the button (the previous fn), and add a new listener (the new fn that was just passed down).

If the child component subscribes to a non-DOM event with the prop, handling the changing of the prop could be more difficult to handle.

So, it could be desirable to pass down a stable prop if possible - but if the child components are already designed properly, it almost certainly won't matter either way.

Note that an identical approach to create an instance variable can be:

const [onViewableItemsChanged] = useState(() => ({ viewableItems }) => {
    setCurrentPageIndex(viewableItems[0].index ?? 0);
});

This has the same effect as:

const onViewableItemsChanged = useRef(({ viewableItems }) => {
    setCurrentPageIndex(viewableItems[0].index ?? 0);
}).current;

because the value in the argument that gets put into the state only occurs when the component mounts.

Related