All About refs
December 1, 2017 ยท View on GitHub
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.
class MyComponent extends React.Component {
setRef = (node) => {
this.node = node
}
render() {
return (
<div ref={this.setRef} />
)
}
}
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.
refon a lowercase<tag />-> DOM node
refon 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:
class Logs extends React.Component {
componentDidMount() {
console.log('cdm');
}
render() {
console.log('render');
return (
<div ref={() => console.log('ref')} />
)
}
}
// Results:
// 'render'
// 'ref'
// 'cdm'
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, as the official docs say:
For example, instead of exposing
open()andclose()methods on aDialogcomponent, pass anisOpenprop 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:
class MyTooBigComponent extends React.Component {
componentDidMount() {
// this.refs.componentToExtract is a DOM node
}
render() {
return (
<div>
{/* ... other stuff */}
<div ref='componentToExtract'>
<span>...</span>
</div>
</div>
)
}
}
If you refactored it to
class MySubComponent extends React.Component {
render() {
return (
<div>
<span>...</span>
</div>
)
}
}
class MyTooBigComponent extends React.Component {
componentDidMount() {
// this.refs.componentToExtract is a component instance!
}
render() {
return (
<div>
{/* ... other stuff */}
<MySubComponent ref='componentToExtract' />
</div>
)
}
}
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.
class MySubComponent extends React.Component {
render() {
return (
<div ref={this.props.refNode}>
<span>...</span>
</div>
)
}
}
class MyTooBigComponent extends React.Component {
componentDidMount() {
// this.componentToExtract is again the DOM node!
}
setRef = (node) => {
this.componentToExtract = node
}
render() {
return (
<div>
{/* ... other stuff */}
<MySubComponent refNode={this.setRef} />
</div>
)
}
}
This is the refNode pattern, and is standardized across all primitives in constelation, a react-native and web prototyping framework my team uses everyday.
refNodepattern: composite components accept arefNodeprop to set outermost DOM element'sref
onLayout
Measuring dimensions of a DOM node is often the reason to set a ref in the first place.
class MyComponent extends React.Component {
componentDidMount() {
if (this.node) {
this.measure()
}
}
measure = () => {
const { height, width } = this.node.getBoundingClientRect()
// ...
}
setRef = (node) => {
this.node = node
}
render() {
return (
<Image refNode={this.setRef} />
)
}
}
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.
class MyComponent extends React.Component {
setRef = (node) => {
// null-check for reason mentioned later
if (node) {
const { height, width } = node.getBoundingClientRect()
// ...
}
}
render() {
return (
<Image refNode={this.setRef} />
)
}
}
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).
class MyComponent extends React.Component {
handleLayout = ({ height, width }) => {
// ...
}
render() {
return (
<Image onLayout={this.handleLayout} />
)
}
}
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.
setRef = (node) => {
this.node = node
}
// ... later
<div ref={this.setRef} />
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 in 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
Edit 11/2017: looks like something like reflective-bind could be a nice solve for inline functions: Ending the debate on inline functions in React โ Flexport Engineering
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
refattribute 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.