How to use React Ref

Using React Ref and really understanding it are two different things. To be honest, I’m not sure I’ve understood everything correctly so far, since it’s not used as much in React as state or side effects, since its API did change a lot in React’s past. In this React Ref guide, I want to give you a step-by-step introduction to React Refs.

React useRef Hook: REFS

React Refs has a strong connection to DOM. That was true in the past, but not anymore after React Hooks were introduced. Ref means reference, but it can be a reference to anything (DOM nodes, JavaScript values…). . So, before diving into the use of React Ref in HTML elements, let’s take a step back and look at React Ref without the DOM. Let’s take the following React component as an example:

function Counter() {
  const [count, setCount] = React.useState(0);
 
  function onClick() {
    const newCount = count + 1;
 
    setCount(newCount);
  }
 
  return (
    <div>
      <p>{count}</p>
 
      <button type="button" onClick={onClick}>
        Increase
      </button>
    </div>
  );
}
Copy the code

React provides the React useRef Hook, which is an API for refs in the React function component. The useRef hook returns us a mutable object that remains unchanged for the life of the React component. Specifically, the returned object has a current property, which holds any modifiable value for us:

function Counter() {
  const hasClickedButton = React.useRef(false);
 
  const [count, setCount] = React.useState(0);
 
  function onClick() {
    const newCount = count + 1;
 
    setCount(newCount);
 
    hasClickedButton.current = true;
  }
 
  console.log('Has clicked button? ' + hasClickedButton.current);
 
  return (
    <div>
      <p>{count}</p>
 
      <button type="button" onClick={onClick}>
        Increase
      </button>
    </div>
  );
}

Copy the code

The ref’s current property is initialized with the argument we provided to the useRef hook (false here). The ref’s current attribute can be reassigned to a new value if needed. In the previous example, we simply kept track of whether the button was clicked.

The key to setting React Ref to the new value is that it does not trigger a re-rendering of the component. Although in the previous example the state updater function (setCount in this case) updates the state of the component and rerenders the component, simply toggled the Boolean value of the ref current property does not trigger rerendering:

function Counter() {
  const hasClickedButton = React.useRef(false);
 
  const [count, setCount] = React.useState(0);
 
  function onClick() {
    // const newCount = count + 1;
 
    // setCount(newCount);
 
    hasClickedButton.current = true;
  }
 
  // Does only run for the first render.
  // Component does not render again, because no state is set anymore.
  // Only the ref's current property is set, which does not trigger a re-render.
  console.log('Has clicked button? ' + hasClickedButton.current);
 
  return (
    <div>
      <p>{count}</p>
 
      <button type="button" onClick={onClick}>
        Increase
      </button>
    </div>
  );
}
Copy the code

Well, we can use the React useRef hook to create a mutable object that will persist for as long as the component exists. But when we change it, it doesn’t trigger rerendering because that’s what state is for, so what’s the use of ref here?

React REF as the instance variable

Ref can be used as an instance variable of a function component in React when we need to track some state without using the React rerender mechanism. For example, we can keep track of whether a component is rendered for the first time or re-rendered:

function ComponentWithRefInstanceVariable() {
  const [count, setCount] = React.useState(0);
 
  function onClick() {
    setCount(count + 1);
  }
 
  const isFirstRender = React.useRef(true);
 
  React.useEffect(() = > {
    if (isFirstRender.current) {
      isFirstRender.current = false; }});return (
    <div>
      <p>{count}</p>
 
      <button type="button" onClick={onClick}>
        Increase
      </button>{/* Only works when setCount triggers a rerender. Changing the ref.current property alone will not trigger rerendering. * /}<p>{isFirstRender.current ? 'First render.' : 'Re-render.'}</p>
    </div>
  );
}
Copy the code

In this example, we initialize the current property of ref with true, because we correctly assume that the component has started its first rendering at its first initialization. However, we then use React’s useEffect hook — a dependency array without setting its second argument — to update the current properties of ref after the component’s first rendering. Setting the current attribute of ref to false does not trigger rerendering.

Now we can create a useEffect hook that only runs the logic on every component update, instead of the initial render. This is of course something every React developer needs at some point, but it’s not provided by the React useEffect hook:

function ComponentWithRefInstanceVariable() {
  const [count, setCount] = React.useState(0);
 
  function onClick() {
    setCount(count + 1);
  }
 
  const isFirstRender = React.useRef(true);
 
  React.useEffect(() = > {
    if (isFirstRender.current) {
      isFirstRender.current = false;
    } else {
      console.log(
        ` I am a useEffect hook's logic which runs for a component's re-render. `); }});return (
    <div>
      <p>{count}</p>
 
      <button type="button" onClick={onClick}>
        Increase
      </button>
    </div>
  );
}
Copy the code

Setting instance variables for the React component using refs is not widely used or often needed. However, having learned about this feature in my classes, I have also seen developers in my React workshops firmly believe that their particular situation requires userEF-generated instance variables.

** Rule of thumb: When you need to track the state of your React component (it should not trigger a re-rendering of the component), you can use the React useRef hook to create an instance variable for it.

React useRef hook: DOM REFS

Let’s take a look at React’s ref specialty: DOM. Most of the time, when you have to interact with HTML elements, you’ll use the React ref. React is declarative in nature, but sometimes you need to read values from HTML elements, interact with HTML element apis, and even have to write values to HTML elements. For these rare cases, the React Refs must be used to interact with the DOM in an imperative rather than a declarative manner.

The React component below shows the most popular example of how the React Ref and DOM API interact:

function App() {
  return (
    <ComponentWithDomApi
      label="Label"
      value="Value"
      isFocus
    />
  );
}
 
function ComponentWithDomApi({ label, value, isFocus }) {
  const ref = React.useRef(); / / (1)
 
  React.useEffect(() = > {
    if (isFocus) {
      ref.current.focus(); / / (3)
    }
  }, [isFocus]);
 
  return (
    <label>
      {/* (2) */}
      {label}: <input type="text" value={value} ref={ref} />
    </label>
  );
}
Copy the code

As before, we use the React useRef hook to create a ref object (1). In this case, we don’t assign any initial value to it, because this will be done in step (2), and we will provide the REF object to the HTML element as a ref HTML attribute. React automatically assigns this HTML element’s DOM node to the REF object for us. Finally (3) we can use the DOM node (which is now assigned to the ref’s current property) to interact with its API.

The previous example shows how to interact with the DOM API in React. Next, you’ll learn how to use refs to read values from DOM nodes. The following example reads the size from our element to display as a title in the browser:

function ComponentWithRefRead() {
  const [text, setText] = React.useState('Some text ... ');
 
  function handleOnChange(event) {
    setText(event.target.value);
  }
 
  const ref = React.useRef();
 
  React.useEffect(() = > {
    const { width } = ref.current.getBoundingClientRect();
 
    document.title = `Width:${width}`; } []);return (
    <div>
      <input type="text" value={text} onChange={handleOnChange} />
      <div>
        <span ref={ref}>{text}</span>
      </div>
    </div>
  );
}
Copy the code

As before, we use the React useRef hook to initialize the ref object and use it in the React JSX to assign the current ref property to the DOM node. Finally, the React useEffect hook reads the element width of the component’s first rendering. You should be able to see the width of your element as the title in the browser TAB.

However, reading the size of the DOM node is only appropriate for initial rendering. If you want to read the state every time it changes, because after all it changes the size of our HTML element, you can provide the state as a dependent variable to React’s useEffect Hook. Whenever the state (text in this case) changes, the element’s new size is read from the HTML element and written to the document’s title property:

function ComponentWithRefRead() {
  const [text, setText] = React.useState('Some text ... ');
 
  function handleOnChange(event) {
    setText(event.target.value);
  }
 
  const ref = React.useRef();
 
  React.useEffect(() = > {
    const { width } = ref.current.getBoundingClientRect();
 
    document.title = `Width:${width}`;
  }, [text]);
 
  return (
    <div>
      <input type="text" value={text} onChange={handleOnChange} />
      <div>
        <span ref={ref}>{text}</span>
      </div>
    </div>
  );
}
Copy the code

Both examples use the React useEffect hook to handle ref objects. We can avoid this problem by using callback ref.

React Callback REF

For the previous example, a better approach is to use something called callback ref. With callback ref, you don’t have to use useEffect and useRef anymore because callback Ref lets you access DOM nodes every time you render:

function ComponentWithRefRead() {
  const [text, setText] = React.useState('Some text ... ');
 
  function handleOnChange(event) {
    setText(event.target.value);
  }
 
  const ref = (node) = > {
    if(! node)return;
 
    const { width } = node.getBoundingClientRect();
 
    document.title = `Width:${width}`;
  };
 
  return (
    <div>
      <input type="text" value={text} onChange={handleOnChange} />
      <div>
        <span ref={ref}>{text}</span>
      </div>
    </div>
  );
}
Copy the code

Callback ref is just a function that can be used for the REF attribute of an HTML element in JSX. This function accesses DOM nodes and is triggered whenever it is used on the REF attribute of an HTML element. Essentially, it does the same side effect as the previous one, but this time the callback ref itself informs us that it has been attached to the HTML element.

Side effects can be run several times with the help of the useEffect dependency array before using the useRef + useEffect combination. You can also augment the callback ref with React’s useCallback hook to run only on the first render of the component:

function ComponentWithRefRead() {
  const [text, setText] = React.useState('Some text ... ');
 
  function handleOnChange(event) {
    setText(event.target.value);
  }
 
  const ref = React.useCallback((node) = > {
    if(! node)return;
 
    const { width } = node.getBoundingClientRect();
 
    document.title = `Width:${width}`; } []);return (
    <div>
      <input type="text" value={text} onChange={handleOnChange} />
      <div>
        <span ref={ref}>{text}</span>
      </div>
    </div>
  );
}
Copy the code

You can also use the dependency array of useCallback Hooks more specifically here. For example, the callback ref callback is executed only when the state (in this case text) changes and, of course, on the first rendering of the component:

function ComponentWithRefRead() {
  const [text, setText] = React.useState('Some text ... ');
 
  function handleOnChange(event) {
    setText(event.target.value);
  }
 
  const ref = React.useCallback((node) = > {
    if(! node)return;
 
    const { width } = node.getBoundingClientRect();
 
    document.title = `Width:${width}`;
  }, [text]);
 
  return (
    <div>
      <input type="text" value={text} onChange={handleOnChange} />
      <div>
        <span ref={ref}>{text}</span>
      </div>
    </div>
  );
}
Copy the code

However, we’ll end up with the same behavior again as before with the useCallback hook without React and just plain callback ref in place — which will be called every time we render.

React REF is used for read and write operations

So far, we have only used DOM refs for read operations (such as reading the size of a DOM node). You can also modify the referenced DOM node (write). The next example shows how to use the React ref style without managing any extra React state:

function ComponentWithRefReadWrite() {
  const [text, setText] = React.useState('Some text ... ');
 
  function handleOnChange(event) {
    setText(event.target.value);
  }
 
  const ref = (node) = > {
    if(! node)return;
 
    const { width } = node.getBoundingClientRect();
 
    if (width >= 150) {
      node.style.color = 'red';
    } else {
      node.style.color = 'blue'; }};return (
    <div>
      <input type="text" value={text} onChange={handleOnChange} />
      <div>
        <span ref={ref}>{text}</span>
      </div>
    </div>
  );
}
Copy the code

You can do this for any property on the DOM node referenced by this. It is important to note that React should not normally be used this way, because it is declarative. Instead, you can use React’s useState hook to set a Boolean value for whether you want the text to be red or blue. However, for performance reasons, it is sometimes useful to manipulate the DOM directly and prevent re-rendering.

We can also manage state in the React component for learning purposes:

function ComponentWithImperativeRefState() {
  const ref = React.useRef();
 
  React.useEffect(() = > {
    ref.current.textContent = 0; } []);function handleClick() {
    ref.current.textContent = Number(ref.current.textContent) + 1;
  }
 
  return (
    <div>
      <div>
        <span ref={ref} />
      </div>
 
      <button type="button" onClick={handleClick}>
        Increase
      </button>
    </div>
  );
}

Copy the code

However, falling down the rabbit hole is not recommended… Essentially, it should only show you how to manipulate any element in React using the React ref attribute and write operations. However, why do we have React instead of plain JavaScript? Hence, the React ref is mainly used for read operations.

This introduction should show you how to use the React ref to reference DOM nodes and instance variables by using the React useRef hook or callback refs. For completeness, I also want to mention the React createRef() top-level API, which is the equivalent of useRef() for the React class component. There are other refs called string refs that are deprecated in React.