All About refs

ref is one of two special props in React — the other being key — because it isn't really a prop. Instead of being passed in to the associated component as a this.props.ref, React snatches it up and uses it to associate a reference to the component's instance or DOM node.

Above example shows setting this.node to the div DOM node via a ref callback. Note: the old ref string way, <div ref='node' /> is deprecated; don't do it!

When is it a DOM node vs. a component’s instance?

If it is placed on a primitive like <div ref={...} />, it will give you the DOM node instance. If it is placed on a composite component like <MyComponent ref={...} />, it will give you the component instance.

ref on a lowercase <tag /> -> DOM node
ref on a PascalCase <Tag /> -> component's instance

Why? Lowercase tags in React are your lowest level primitives. React treats them different from your custom composite components, which can not be lowercase.

When is the ref first set?

After the first render(), but before componentDidMount(), your ref will be set.

Feel free to see for yourself:

When would you want a ref?

OK, you know how to get a ref, what it points to, and when it is set, but when should you actually reach for this power?

Ideally, never! refs impact performance by disabling some production babel transforms (inline-elements and constant-elements). Netflix even goes as far as entirely preventing their custom renderer from using refs (source) for component inlining.

And you may be able to forgo a ref with declarative props. From the official docs:

instead of exposing open() and close() methods on a Dialog component, pass an isOpen prop to it.

But realistically, you’re likely to need some imperative functionality: animating, media playback, DOM measurements, etc. For example, an Animate component may be more unwieldy managing an isAnimating prop than just calling this.animate.trigger().

The cool stuff

With ref callbacks, we can do some pretty cool stuff!

refNode pattern

Back when ref strings were the norm, it was difficult to extract out subcomponents:

If you refactored it to

you lose access to the DOM node. But with ref callbacks, you can pass the callback as a non-ref prop, and the subcomponent can set the ref correctly.

This is the refNode pattern, and is standardized across all primitives in constelation, a react-native and web prototyping framework my team uses everyday.

refNode pattern: composite components accept a refNode prop to set outermost DOM element’s ref

onLayout

Measuring dimensions of a DOM node is often the reason to set a ref in the first place.

This adds quite a bit of boilerplate to accomplish a simple task. Let’s do better by combining our learnings of when a ref is set with the power of ref callbacks.

Cool! That radically reduces our boilerplate. We don’t even need to store the ref for later use. In constelation, we've gone one step further in reducing boilerplate for our primitives. We abstracted the above code into a simple onLayout prop (similar to react-native, but the measure only happens once).

Shoutout to Mike Hobizal — @openmike503 for the idea!

linkRef

Alright, fine, ref callbacks give us more power than ref strings, but using them the correct way requires much more boilerplate.

vs. <div ref='myRef' />

Wouldn’t it be nice to use ref callbacks with the same DX (developer experience) of ref strings?

Well, if you’re using Preact, you’re in luck. linkref, from Preact’s creator, provides an abstraction for creating and setting function refs.

Unfortunately,linkref does not currently work with React. I'm hoping it will someday, or a future babel plugin will accomplish this. babel-plugin-transform-jsx-ref-to-function comes close, but it uses inline ref callbacks, which hurts performance (as mentioned below). This tweet thread with @thejameskyle is also relevant.

The gotchas

Don’t inline ref callbacks

Code examples look so much simpler and easier to follow when ref callbacks are inlined:

<div ref={node => this.node = node} />

Which is why so many tutorials show them inlined, but don’t bring this bad practice into your code! Arrow and bind functions in a render() produce a performance hit by creating a new function on EVERY re-render.

Do the right thing:

<div ref={this.setRef} />

ref can become null after it is set

I’ll defer to Dan Abramov for this one:

Ever wondered why callback refs get called with null during the updates? I wrote a bit about this: https://t.co/42kdCy0eKF — Dan Abramov
That’s a very long way to say “null means unmount, use it as a signal to perform cleanup” 😉😊 — Glen Mailer

No ref for functional components

From the official docs:

You may not use the ref attribute on functional components because they don’t have instances.

Speaking of the official docs, if it has been a while, I recommend you re-read them! They have really improved recently.


Originally published on the react-playbook.