October 31, 2019

WTF is Up with Refs?

A friend of mine who was trying to write custom hooks with useRef mentioned that refs were generally kind of confusing to him. After chatting through the things he wasn’t clear on, I realized that there were a few key concepts that aren’t necessarily obvious, but that give you the fundamental understanding to build on when using refs in other contexts:

Refs are just plain JavaScript objects - not special React things

Really. I promise. They’re just simple JS objects that have a current key that can be set to any value. They’re basically just {current: foo}. If you notice, there’s nothing particularly exciting or React-specific about {current: foo}.

When passed as a prop, React sets ref.current to the new DOM nodes

While refs themselves aren’t exciting or special, React uses refs for React-specific behavior: if you pass in ref={myRef} as a prop to a React component, React will make sure that myRef.current will always return the DOM node of that element. It might be more complicated (I haven’t looked it up) but my mental model of Reactworld is:

  • after mounting the component after the first render, set ref.current = element
  • if a new element is created as part of rerendering, update ref.current to the new element
  • after unmounting, set ref.current = null

Updating a ref doesn’t trigger a rerender

State is what you should use if data changing should trigger a rerender. Refs are used when you need to keep a piece of mutable data across the entire lifecycle of the component, but that isn’t a part of the data-rendering cycle. In class components, this is usually maintained as just an instance variable; in hook land, useRef is the way to go.

If you understand these three things about refs, then the useRef hook starts to make a lot more sense. This knowledge is a prerequisite for my next post, where we’ll talk about how to design custom hooks that use refs, and how to avoid some common pitfalls.