Photo by rawpixel.com on Unsplash

React makes it easy to almost never worry about the underlying HTML/JavaScript Document Object Model. React’s retained mode rendering allows you to define everything on a web page declaratively, including interactive and rendering state. It’s great!

But every once in a while you need to reach back into the DOM to manage state imperatively. You might need to select some text on a page, or manage the focus of a form element. For these cases, React provides ref callbacks.

Here’s an example of a very simple React Component class that uses the ref callback feature.

Import { Component } from ‘react’; class ExampleComponent extends Component {

setFocus() {

this.textInput.focus();

} render() {

return (

<input type=”text”

ref={(input) => { this.textInput = input; }} />

);

}

}

Here’s how the example works: when the <input> element is rendered, React calls the function defined in the ref attribute, passing that function the <input> element as an argument.

This is pretty much the same example you’ll find in the official React docs, and in most tutorials about React refs. However, if you want to do anything more complicated than just save a reference to the element/component, you should not use this pattern.

We use refs in the Daily.co codebase for managing media and canvas elements. We often need to do something the first time an element is mounted. So, something like this:

class CanvasWithAttitude extends Component {

setupToDraw(canvas) {

// ...

} render() {

return (

<canvas ref={(canvas) => { this.setupToDraw(canvas); }} />

);

}

}

Except we don’t do that, because with the code above, the setupToDraw() function will actually get called twice for every render. That’s definitely not what we want.

Each time the component renders, React needs to recreate any inline or bound functions that are defined as element attributes. To clear out old references, it calls the inline or bound function first with a null argument. Then it calls the function again with the element/component argument. (For the explanation of this in the official React docs, scroll down to the very bottom of the Refs and the DOM page.)

Notice I wrote “inline or bound,” so this isn’t the right thing, either:

<canvas ref={ this.setupToDraw.bind(this); } />

Our goal is for the ref callback to get called once, when the component mounts.

One fix is to use the ES7 class property syntax to define the function.

class CanvasWithAttitude extends Component {

setupToDraw = (canvas) => {

// ...

} render() {

return (

<canvas ref={ this.setupToDraw } />

);

}

}

This creates a new setupToDraw() function for each instance of CanvasWithAttitude , and assigns that function to the setupToDraw property of the object. In effect, we’ve moved the inline function definition out of our render function and asked our JavaScript transpiler to do it for us invis-magically at instance creation time. :-)

We have code like this in many of our classes, but I don’t love this pattern. There are lots of corner cases in the JavaScript object model, and I find it easy to forget that the code above does a couple of non-obvious things. First, the setupToDraw() function is created for each new instance of the class, and second, the function does not exist as part of the class prototype (so it won’t be inherited, for example).

Here’s how I prefer to write this pattern.

class CanvasWithAttitude extends Component {

constructor() {

super();

this.setupToDrawBound = this.setupToDraw.bind(this);

} setupToDraw(canvas) {

// ...

} render() {

return (

<canvas ref={ this.setupToDrawBound } />

);

}

}

Here, setupToDrawBound() is still a new function that’s created each time an instance is constructed, but setupToDraw() is a normal member function, and this use of bind() is easy to reason about when scanning the code. Finally, both setupToDraw() and setupToDrawBound are inherited by any classes that extend CanvasWithAttitude .