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!
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.
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'
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! ref
s 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 ref
s (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 aDialog
component, pass anisOpen
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()
.
With ref
callbacks, we can do some pretty cool stuff!
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.
refNode
pattern: composite components accept arefNode
prop to set outermost DOM element'sref
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!
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 ref
s.
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.
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} />
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
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.