Properly Include Component Function from useEffect and useCallback Dependency List

Created:

I was working on Slots (letting a portal component render into a potentially not mounted portal home) when initially I was violating a hooks rule. Namely, I did not include the setSlot function I called within the useCallback body inside its dependency list.

export const SlotHome = ({ slotName, className }) => {
  const [, setSlot] = useSlot(slotName)
  const ref = React.useCallback(node => {
    // we're lying to hooks because we're using setSlot without including it in the deps list
    setSlot(node)
  }, [])
  return <div className={className} ref={ref} />
}

But if I do include it, the function is different every time the component render is called and therefore would re-render like a maniac. Without including it, the implementation still works, but we're lying to hooks and it is strongly advised against.

This made me finally decided (cuz it says it's a 49-min read) to read Dan Abramov's article, A Complete Guide to useEffect and decided to properly figure this out. Here are some key takeaways:

The highlighted takeaway was what solved this problem. The idea is to tell hooks that the function won't change unless what we specify in that useCallback's dependency list change.

const [, setSlot] = useSlot(slotName)
// specify that setSlotMemo won't change unless slotName changes
const setSlotMemo = React.useCallback(setSlot, [slotName])

// now ref is safe to include setSlotMemo in its deps list
const ref = React.useCallback(
  node => {
    setSlotMemo(node)
  },
  [setSlotMemo]
)

Once again Dan's article covers a complete guide to properly use useEffect (which extends to useCallback because they share the same signature).

Relevant links