What is the significance of Keys and refs in React?

Keys:

Keys in React are used to identify unique VDOM Elements with their corresponding data driving the UI; having them helps React optimize rendering by recycling existing DOM elements. Let’s look at an example to portray this.

We have two <TwitterUser> Components being rendered to a page, drawn in decreasing order of followers:

-----------
| A - 103 |
-----------
-----------
| B - 92  |
-----------

Let’s say that B gets updated with 105 Twitter followers, so the app re-renders, and switches the ordering of A and B:

-----------
| B - 105 |
-----------
-----------
| A - 103 |
-----------

Without keys, React would primarily re-render both <TwitterUser> Elements in the DOM. It would re-use DOM elements, but React won’t re-order DOM Elements on the screen.

With keys, React would actually re-order the DOM elements, instead of rendering a lot of nested DOM changes. This can serve as a huge performance enhancement, especially if the DOM and VDOM/React Elements being used are costly to render.

Keys themselves should be a unique number or string; so if a React Component is the only child with its key, then React will repurpose the DOM Element represented by that key in future calls to render().

Let’s demonstrate this with a simple list of todos rendered with React:

Interactive code sample available on Matthew Keas’ github.

class List extends Component {
    constructor(p){
        super(p)
        this.state = {
            items: Array(5).fill(1).map((x,i) => ({id:i}))
        }
    }

    componentDidMount(){
        const random = (a,b) => Math.random() < .5 ? -1 : 1

        setInterval(() => {
            this.setState({
                items: this.state.items.sort(random)
            })
        }, 20)
    }

    render() {
        let {items} = this.state
        return <ul>
            {items.map(item =>
                <li key={item.id}>{item.id}</li>)}
        </ul>
    }
}

DOM.render(<List />, document.body)

The setInterval() occurring on mount reorders the items array in this.state every 20ms. Computationally, if React is reordering the items in state, then it would manipulate the DOM elements themselves instead of “dragging” them around between positions in the <ul>.

It is worth noting here that if you render a homogenous array of children – such as the <li>’s above – React will actually console.warn() you of the potential issue, giving you a stack trace and line number to debug from. You won’t have to worry about React quietly breaking.

React will determine whether it is the same component or not based on key

And when the key is not provided? Well… React will automatically use an increasing integer number; I suspect based on the data-reactid attribute in DOM.

So I was thinking that I am rendering conceptually a new component and react was actually rendering the old one. The fact that I changed props doesn’t matter. Immutability of props is just a convention, not a requirement. And react doesn’t care. Because it was the same component getInitialState was not called and the component remembered its old state.

 

Refs:

Similarly to keys, refs are added as an attribute to a React.createElement() call, such as <li ref="someName"/>. The refserves a different purpose, it provides us quick and simple access to the DOM Element represented by a React Element.

Refs can be either a string or a function. Using a string will tell React to automatically store the DOM Element as this.refs[refValue]. For example:

class List extends Component {
    constructor(p){
        super(p)
    }

    _printValue(){
        console.log(this.refs.someThing.value)
    }

    render() {
        return <div onClick={e => this._printValue()}>
            <p>test</p>
            <input type="text" ref="someThing" />
        </div>
    }
}

DOM.render(<List />, document.body)

this.refs.someThing inside componentDidUpdate() used to refer to a special identifier that we could use with React.findDOMNode(refObject) – which would provide us with the DOM node that exists on the DOM at this very specific instance in time. Now, React automatically attaches the DOM node to the ref, meaning that this.refs.someThing will directly point to a DOM Element instance.

Additionally, a ref can be a function that takes a single input. This is a more dynamic means for you assign and store the DOM nodes as variables in your code. For example:

class List extends Component {
    constructor(p){
        super(p)
    }

    _printValue(){
        console.log(this.myTextInput.value)
    }

    render() {
        return <div onClick={e => this._printValue()}>
            <p>test</p>
            <input type="text" ref={node => this.myTextInput = node} />
        </div>
    }
}

DOM.render(<List />, document.body)

 

Refs are a great way to send a message to a particular child instance in a way that would be inconvenient to do via streaming Reactive props and state. They should, however, not be your go-to abstraction for flowing data through your application. By default, use the Reactive data flow and save refs for use cases that are inherently non-reactive.

Benefits:

  • You can define any public method on your component classes (such as a reset method on a Typeahead) and call those public methods through refs (such as this.refs.myTypeahead.reset()). In most cases, it’s clearer to use the built-in React data flow instead of using refs imperatively.
  • Performing DOM measurements almost always requires reaching out to a “native” component such as <input /> and accessing its underlying DOM node using a ref. Refs are one of the only practical ways of doing this reliably.
  • Refs are automatically managed for you! If that child is destroyed, its ref is also destroyed for you. No worrying about memory here (unless you do something crazy to retain a reference yourself).

Cautions:

  • Never access refs inside of any component’s render method – or while any component’s render method is even running anywhere in the call stack.
  • If you want to preserve Google Closure Compiler advanced-mode crushing resilience, make sure to never access as a property what was specified as a string. This means you must access using this.refs['myRefString'] if your ref was defined as ref="myRefString".
  • If you have not programmed several apps with React, your first inclination is usually going to be to try to use refs to “make things happen” in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to “own” that state is at a higher level in the hierarchy. Placing the state there often eliminates any desire to use refs to “make things happen” – instead, the data flow will usually accomplish your goal.
  • Refs may not be attached to a stateless function, because the component does not have a backing instance. You can always wrap a stateless component in a standard composite component and attach a ref to the composite component.

Your email address will not be published.