React Refs – A Complete Guide

React Refs – A Complete Guide

The React core team announced React.js version 16 in 2017, which included the concept of refs. It was a game changer because it gave developers the ability to access actual DOM nodes from React. Today, almost all React component libraries make advantage of this fantastic React functionality.

In this article, we’ll look at React Refs and their practical applications. We’ll go over a lot of examples here, so stick around until the end.

What is a ref in React.js?

A ref in React.js.
A ref in React.js.

In React.js, a ref is essentially a reference to a DOM element or an instance of a component. Refs provide us with an API to access and modify DOM elements without using props, states, etc. For example, if we attach a ref to a DOM element, we can use it later to focus, click, etc. Not only that, but refs are super useful when we need DOM element measurements such as height, width, position, etc.

Is a ref only useful for referencing DOM elements?

We may choose to use refs for essentially anything and not just DOM elements. A ref is simply an object with a current property that holds a value. This value, like the state variables, is persistent across renders. Except for a few dissimilarities, the state, and ref share many similarities.

What is the difference between states and refs?

While you might think that states and refs are the same things, they are not. Let us look at some of their differences:

  • We can mutate a ref just like any other JavaScript object, and its value will be preserved across re-renders. This is not the case with state variables. We need to use a separate function to update them.
  • When refs are updated, no re-renders occur, whereas states cause the component to re-render.
  • Updating a ref, like updating other objects, is a synchronous process. That is, the updated value of the ref is available immediately following the update. States, on the other hand, are updated asynchronously.

Why is the useRef hook used?

The useRef is used for creating a ref in React Functional Components (RFCs). Its usage is similar to that of any other hook in React, in that we simply call the hook and it returns the ref.

const ref = useRef();Code language: JavaScript (javascript)

The ref object has a current property that holds the current reference values. By accessing this current property, we can read and update the value.

ref.current = 'Codedamn Playgrounds';Code language: JavaScript (javascript)

How to grab HTML DOM elements using refs?

We’ve seen that we can use refs to grab HTML DOM elements in React. Doing this is pretty straightforward, we just need to attach a ref to a DOM element by passing it as the ref attribute.

We’ll create a ref using the useRef hook:

const divRef = useRef();
Code language: JavaScript (javascript)

Then attach it to the element using the ref attribute:

return <div ref={divRef}>Div element</div>;Code language: JavaScript (javascript)

The DOM element can be accessed via our ref’s current property. You can log the value of divRef.current to check if it contains the element.

console.log(divRef.current);Code language: JavaScript (javascript)
Grabbing a DOM element using refs.
Grabbing a DOM element using refs.

We can do a whole bunch of things with this DOM element. For example, adding/removing CSS classes, clicking the element, focusing on elements, etc.

divRef.current.classList.add('visible');
divRef.current.focus();
divRef.current.click();Code language: JavaScript (javascript)

We can do a lot more using this. For example, we may even transfer a ref from the parent component to its child component using Forward Refs. We’ll go over forward refs in more detail later.

Where to use the ref inside a component?

The value of a ref is undefined on the initial render. This happens because initially there is no DOM element present, therefore the ref contains undefined.

When the component mounts, React assigns the value to the ref, i.e. it attaches the current property containing the DOM element. When the component unmounts, the value is reset to NULL.

Therefore, before using a ref, we must make sure that the component is mounted. We do this by using the useEffect hook.

The useEffect hook’s callback runs when the component mounts and again when the dependencies change.

useEffect(() => {
  // Run this code after the component is mounted
}, [dependency1, dependency2]);Code language: JavaScript (javascript)

Therefore, it is a perfect place to use the refs to perform some initial operations. Additionally, we can use refs within event handler methods, and other callbacks.

useEffect(() => {
  divRef.current.classList.add('visible');
  divRef.current.focus();
  divRef.current.click();
}, []);Code language: JavaScript (javascript)

What other uses are there for ref besides grabbing DOM elements?

We’ve seen that, like states, refs can be used as stores, with a special property that updating refs does not cause the component to re-render.

Therefore, we can use refs to store some values that we want to prevent any re-rendering whenever they change.

For example, you might want to check whether a component is still mounted or not. This could happen inside modals that perform asynchronous operations.

Updating the state of an unmounted component.
Updating the state of an unmounted component.

If we close the modal in the middle of the processing, React will try to update some state after the processing is complete, but it will throw a warning: “Can’t perform a React state update on an unmounted component”.

We get this warning because we’re trying to update the state of an unmounted component. This may also result in a memory leak. To prevent this, before updating the state, we’ll check if the component is still mounted. We’ll do this using a ref.

const isMounted = useRef();

useEffect(() => {
  // Runs when component is mounted
  isMounted.current = true;
  
  // Runs when component is unmounted
  return () => {
      isMounted.current = false;
  };
}, []);Code language: JavaScript (javascript)

After the first render, the value of the current property is set to true. When the component unmounts, the useEffect hook’s cleanup function runs and resets the ref’s current property to false.

Using the current property on the ref, we can determine whether or not the component is still mounted.

const data = await getData();

// Check if component is mounted before updating state
if (isMounted.current) {
  // Update the state
  setData(data);
}Code language: JavaScript (javascript)

This way, we can ensure that there are no warnings or memory leaks within the application.

Some advance concepts

After learning the fundamentals, it’s time to move on to more advanced ref concepts, such as forwarding and callback refs. Let’s go over them one by one.

Forwarding Refs

Consider the following scenario: you have a modal form and you want to focus on the modal form’s first input when it opens.

function ModalForm(props) {
  return (
    <form>
      <h1>Modal</h1>
      <input type='text' />
      <button type='submit'>Submit</button>
    </form>
  );
}Code language: JavaScript (javascript)

You might think of doing something like this:

const inputRef = useRef();

return (
  <div>
    <ModalForm ref={inputRef} />
  </div>
);Code language: JavaScript (javascript)

However, if this is done, the value of the inputRef will be the DOM instance of the entire modal. It is not possible to capture this ref inside the modal and assign it to the input.

To address this problem, we employ the Forwarding Refs technique. A component can use this technique to forward the provided ref to its child. As a result, the ref actually holds the child’s DOM instance.

To do ref forwarding, a component must be defined using the React.forwardRef() method.

const ModalForm = React.forwardRef((props) => {
  return (
    <form>
      <h1>Modal</h1>
      <input type='text' />
      <button type='submit'>Submit</button>
    </form>
  );
});Code language: JavaScript (javascript)

This allows the component to receive the provided ref as the second argument.

const ModalForm = React.forwardRef((props, ref) => {
  return (
    <form>
      <h1>Modal</h1>
      <input type='text' ref={ref} />
      <button type='submit'>Submit</button>
    </form>
  );
});Code language: JavaScript (javascript)

This ref can be assigned to the desired input within the component. We can log the value of inputRef.current to check it contains the input element.

useEffect(() => {
  console.log(inputRef.current)
}, [])Code language: JavaScript (javascript)
Forwarding a ref from parent to its child component.
Forwarding a ref from parent to its child component.

Callback Refs

Another option for assigning a ref is to use callback refs. You can use this to pass a callback to the ref attribute. The DOM instance of that element will be passed as an argument to this callback. This DOM element can be assigned to a ref or a state, or we might do something with it right away.

handleRef = (element) => {
  console.log(element);
};

return <input ref={handleRef} />;Code language: JavaScript (javascript)
Using callback refs.
Using callback refs.

React runs this callback on the component mount and unmount. When the component mounts, React calls it with the element’s DOM instance, and when it unmounts, it calls it with NULL.

When should you use a ref?

While you may believe that refs can be used for any purpose, they should be avoided for the majority of tasks. You can apply the golden rule that I use: “If something can be done declaratively, do it declaratively.”

To avoid confusion about declarative versus imperative programming, here’s a small definition of each. In declarative programming, you describe what to do. In imperative style, you specify how to do it.

For example, when comparing React (declarative) and jQuery (imperative), you’ll notice that in React, you just define how the final UI should look, without worrying about how the state is handled, or how the new UI is transitioned from the older UI on state updates. However, in jQuery, you are responsible for all UI transitions from the old to the new state. You must use CSS classes and IDs to select current elements, add/remove necessary CSS classes, apply inline styles, and whatnot. This is the most significant distinction between declarative and imperative programming.

Applications of refs

After learning everything from the fundamentals to the advanced concepts of refs, it is time to learn about some of the applications of refs.

We’ll only cover the most common applications of ref here, but you’re not limited to them and can do all sorts of amazing things (but remember the golden rule).

Focus on elements

You can use refs to grab a DOM element such as an input, button, etc., and focus on it. For example, you can focus on an input element when a button is clicked. Here’s how:

export default function FocusOnElement() {
  const inputRef = useRef();

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} />
      <button onClick={handleClick}>Focus</button>
    </div>
  );
}Code language: JavaScript (javascript)

You may try it out with the Codedamn Playground embedded below:

Obtaining an element’s size and position

Just like focusing on an element, we can use the getBoundingClientRect() method to obtain the size and position measurements of an element.

export default function ObtainSizeAndPosition() {
  const divRef = useRef();

  useEffect(() => {
    const { width, height, top, left } = divRef.current.getBoundingClientRect();
    console.log(width, height, top, left);
  }, []);

  return <div ref={divRef}>Obtain Size and Position</div>;
}Code language: JavaScript (javascript)

Using it for caching

You can use refs to cache important results and avoid performing heavy computations repeatedly. Because refs do not cause re-renders on updates, they can serve as an ideal cache-store.

const fetchData = async (url) => {
  // Check if data is cached
  if (ref.current[url]) {
    return ref.current[url];
  }

  const data = await getData(url);

  // Cache data for the current URL
  ref.current[url] = data;

  return data;
};Code language: JavaScript (javascript)

Counting the number of re-renders

You may be aware that our components in React 18 render twice when in strict mode in development. It was done to detect any unintentional/accidental side effects that may be present.

It may be a big problem for beginners because it goes against expected behavior.

Well, not to worry, React refs have got our back. We can count the number of re-renders our component performs by using refs. This is how you do it:

export default function Counter() {
  const [counter, setCounter] = useState(0);
  const rerenderRef = useRef(0);

  const increment = () => {
    setCounter(counter + 1);
  };

  rerenderRef.current = rerenderRef.current + 1;

  return (
    <div className='counter'>
      <h2>Counter without strict mode</h2>
      <button onClick={increment}>Counter: {counter}</button>
      <p>Number of re-renders: {rerenderRef.current}</p>
    </div>
  );
}Code language: JavaScript (javascript)

When you click the button, a counter is incremented. Following the increment of the counter, we’ll increment a value within our ref to keep track of the number of re-renders.

When we wrap this component in a <React.StrictMode> component, we get a different result than when we don’t use strict mode.

You may try it out with the Codedamn Playground embedded below:

When both counters are pressed five times, the component without strict mode re-renders five times (excluding the initial render), whereas the counter with strict mode re-renders ten times, or twice as many as the component without strict mode.

Conclusion

A ref is a powerful feature of React. It enables us to grab HTML DOM elements by adding a ref attribute to JSX elements. It also allows us to store anything inside a ref. Refs have a unique property that prevents re-rendering when they are updated.

When we need to pass a ref from a parent component to a child component, we can use the forwarding ref technique. Another fantastic feature is the callback ref, which allows us to pass a callback to the ref attribute instead of a ref object, which receives a DOM element as an argument. We can do a whole bunch of stuff with it.

Overall, refs were a powerful addition to React and are widely used in component libraries such as MaterialUI, ChakraUI, and others today. You can use the power of refs to create amazing React applications.

The source code for all snippets used may be found by visiting the Codedamn Playground.

This article hopefully provided you with some new information. Share it with your friends if you enjoy it. Also, please provide your feedback in the comments section.

Thank you so much for reading 😄

Sharing is caring

Did you like what Varun Tiwari wrote? Thank them for their work by sharing it on social media.

0/10000

No comments so far