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:
- class components mutate state
- functional components + hooks retain the proper closure behavior (always use closed variable, no mutating crap)
-
3 ways to optimize dependency list
- remove unnecessary use of params in component lifecycle scope
- use reducer
- memoize functions with
useCallback
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).