When making a custom hook that needs a reference to a DOM node, your first guess would be to use the new
useRef hook. It was certainly mine.
So naturally I went with that for my first implementation of Hooks in react-intersection-observer — But then an interesting issue came up, that I hadn’t considered.
[Question] How would you handle conditional rendering with hooks? · Issue #162 ·…
First, I'd like to say thank you for such a great tool. Very useful. I ran into a situation I wanted to run by you, and…
Essentially, I was assuming that a
ref would always be defined, but —
undefinedto begin with, and then set later.
refcould change to a different element.
refcould change from an element to
My hook wasn’t handling any of these cases.
The problem with refs and useEffect
useRef hook can be a trap for your custom hook, if you combine it with a
useEffect that skips rendering. Your first instinct will be to add
ref.current to the second argument of
useEffect, so it will update once the
ref isn’t updated till after your component has rendered — meaning, any
useEffect that skips rendering, won’t see any changes to the
ref before the next render pass.
Looking at the community created hooks, this seems like a fairly widespread issue right now. Great Hook collections like usehooks.com and palmerhq/the-platform falls into this trap — most of the time because they assume the
ref won’t change, and it’s set during the first render.
I went ahead and asked Dan Abramov how to solve this issue:
The React team did try to warn us about errors if we skip effects.
useRef, you should create a ref callback with
Once the callback is triggered with a node, you can consume it as needed.
The React FAQ has now been updated with an example of using a ref callback to measure the size of DOM elements. This is also a solution to this exact problem, so make sure to read that entry.
You could argue that hooks can assume the
ref is set when created, but I’ve already seen multiple cases of developers running into this issue, unsure of why their hook isn’t working. Seeing how simple the solution is, I think it’s important that all custom hooks start to implement it.
Keep in mind this is only a problem if your
useEffect hook doesn’t trigger on all renders, so keep using
useRef in those hooks.