React’s useRef hook explained

The name ‘useRef’ really threw me off. It made me think this hook did one thing, when really it does something else entirely. To stop other people getting all confused like I did, I wrote this post explaining exactly what the useRef hook is, and how to use it.

In the world of React we think of refs as being references to actual DOM nodes that react is rendering. These are useful for performing operations that require actual DOM access, like focusing an element or measuring an element.

Take this understanding of what a ref is. Add the fact that ‘ref’ is in the hook’s name. Add the fact that the React docs have the following example code for useRef:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

(Taken from https://reactjs.org/docs/hooks-reference.html#useref)

Combine these all together and you could be forgiven for thinking that useRef is meant for storing refs to DOM nodes. This isn’t actually what they are for.

useRef is there to give function based components the ability to store data across the whole lifetime of a component instance. We can already store data like this using useState, but where useRef differs is that whereas changing the value of data stored in the state will trigger a new render, changing the data stored using use ref will not. In this way it is the function based components equivalent of using properties on this to store data.

Say we wanted a count of how many times a component had rendered, but we didn’t want increasing the count to trigger a render (that’s an infinite loop waiting to happen!).

Using a functional component, we could do it using properties on the component instance:

class MyCountingComponent extends React.Component {
  constructor () {
    this.count = 0;
  }

  render () {
    this.count++;
    return (<div />);
  }
}

Here this.count is used to store the count. It is incremented on each render, and this does not trigger a re-render.

count is the same variable throughout the entire life of the component instance, as it is stored on the instance.

Imagine if we tried this in a functional component:

const MyCountingComponent = () => {
  let count = 0;
  count++;

  return (<div />);
}

This isn’t going to work, is it?

count will always be 1 after each render. The problem here is that this is essentially a new count variable being created for every render. We need a reference to count variable which will persist across the entire lifetime of the component instance. We can then simply increment the value held in the reference.

useRef to the rescue:

const MyCountingComponent = () => {
  const count = useRef(0);
  count.current++;
  return (<div />);
}

The first time the render is run useRef will create an object with a current property set to 0 for us. On subsequent renders useRef will always return a reference to that same object, therefore when we change the value of current, this change is persisted between renders. This is why an object with a current property has to be used. If we were just using a number here for count, instead of a number wrapped in an object we couldn’t get the persistence between renders that we want.

Of course this example of useRef was f**king useless, so in my next post I’ll actually create a hook using useRef that is so useful you’ll probably want to use it in your own code.

Hope this helps,

WPG

One comment

  1. Thanks for writing this, it definitely makes things clearer! I keep seeing React hooks around but never got to diving deep into it, and I think you gave here a good example and a starting point for learning more about it. Looking forward to your next article!

Leave a Reply

Your email address will not be published. Required fields are marked *