useRef Hook in React Explained

A Simple Guide for Beginners

useRef Hook in React Explained

Introduction

Welcome to a journey that's going to simplify your React development experience. If you're just starting out with React or feeling a bit puzzled by some of its concepts, you're in the right place. Today, we're demystifying one of the essential tools in the React toolbox: the useRef hook.

Our mission here is straightforward: to help you understand the useRef hook, why it matters, and how to use it effectively. This humble hook holds incredible power when it comes to managing references to DOM elements and mutable values, and grasping it is a key step in your React journey.

But why is understanding useRef so crucial? In React development, you'll often encounter situations where you need to interact with the DOM, handle user input, or manage mutable data efficiently. Without a solid grasp of useRef, you might find yourself stuck or resorting to less-than-ideal solutions.

So, whether you're new to React or looking to strengthen your foundation, let's dive into the world of useRef and unlock its potential. Your React journey is about to become a whole lot more exciting! 🚀🔗

Prerequisites

Before we dive into the world of the useRef hook in React, let's ensure you have the basics covered:

  • Familiarity with JavaScript fundamentals is essential. If you're new to JavaScript, consider exploring the basics before diving into React.

  • While this blog is aimed at beginners, having a basic understanding of React concepts like components and states will be helpful. You don't need to be an expert, a little prior exposure is sufficient.

  • Make sure that you can follow along with your local development environment or use any online environments like Stackblitz

What are Hooks in React? 🪝

Let’s start with what we already know, we can create two types of components in react. They are class components and functional components. Earlier to React 16.8, class components were so powerful with all the React features. React version 16.8 introduced Hooks for functional components making them as powerful as class components. Hooks provides a more intuitive way to work with state management and lifecycle methods in functional components which were primarily associated with class components earlier.

A few commonly used hooks:

useState: useState allows functional components to manage local component states. It takes an initial state value as an argument and returns an array containing the current state and a function to update it

useEffect: useEffect is used for handling side effects in functional components. You can use it to perform tasks like data fetching, DOM manipulation, and more.

useContext: useContext simplifies the process of passing data and functions down to deeply nested components.

These are just a few examples. React provides several other hooks like useReducer, useMemo, useCallback, useRef etc. You can explore more about them from their docs here.

Intro to useRef Hook

Now, let's dive into the useRef hook. I expect that you have a basic understanding of the useState hook, which is great for managing the state inside components. One of its advantages is that it triggers a component re-render every time the state changes. If you're new to useState, I recommend getting comfortable with it before diving into useRef.

Now, what's intriguing about useRef is that it also serves as a tool for managing the state, but with a unique twist – it won't trigger a re-render when the state updates. You might wonder, "Why to use useRef if it doesn't trigger rendering?" Well, rendering is quite an intensive task, and there are scenarios where you want to maintain a state without incurring the cost of rendering which might sometimes make your application a bit fast. This is precisely when useRef comes to the rescue.

useRef is a React Hook that lets you reference a value that’s not needed for rendering.

Use cases of useRef Hook

Now that we grasp where the useRef hook can shine, let's explore some real-life situations where developers have already begun harnessing its power. These practical use cases shed light on how useRef can be a game-changer in optimizing React applications.

  • Developers use useRef to interact with and manipulate DOM elements directly. Common scenarios include focusing input fields, scrolling to specific elements, or triggering animations. For example, you might use useRef to focus on an input field when a modal opens, ensuring a seamless user experience

  • In some situations, developers use useRef to store previous values or references to objects, helping optimize component updates by preventing unnecessary re-renders.

  • When integrating third-party libraries or plugins into a React application, developers often use useRef to create a reference to a DOM element required by the library. This allows for seamless integration and interaction with external code that relies on specific DOM elements.

  • For applications that involve HTML5 canvas or WebGL for rendering graphics and animations, useRef can be used to reference the canvas element and interact with the rendering context directly.

  • Infinite scrolling is a common UI pattern in web applications. useRef can be used to create a reference to the scroll container and monitor scroll positions, triggering additional data loading when the user reaches a certain point in the scroll.

These real-life use cases demonstrate the versatility of the useRef hook in React. It empowers developers to work more effectively with the DOM, create smooth user experiences, and seamlessly integrate third-party libraries into their applications. Now let’s dive into how we can start using this useRef hook.

Basic Usage of useRef

The useRef hook in React is commonly used to store a mutable value that persists across component renders or to create a mutable reference to a DOM element. This means we can have a value that can change, and it doesn't reset when the component re-renders. It "persists" across renders, which means it sticks around even if the component refreshes for other reasons.

Import the useRef hook

import {useRef} from 'react';

Instantiate the hook inside your functional component.

const ref = useRef(initialValue);

Here, ref can be any variable name that you prefer and initalValue is the value that you want to store when you instantiate the ref variable.

Let’s use this hook in our component.

import React, { useRef } from 'react';

export default function App() {

 const countRef = useRef(0);
 console.log(countRef);

  return (
    <div>
      Hello World
    </div>
  );
}

I expect that you already know how to create a react app. So, In App.js file I removed all the unnecessary code and used the useRef inside that functional component.

 const countRef = useRef(0);

Here I used the variable name as countRef and with the initial value of 0. The useRef returns an object with a single property. The single property name is current.

The value for the current property will be set to the initialValuethat we passed in the useRef hook. If I console the countRef, we can see the object with current property.

const countRef = useRef(0);
console.log(countRef);

The Output:

Read this if you see two console logs of the ref object on the console
If you see two logs in your console, then it's totally fine. Because in development react renders each component twice to help find some issues in your components. It is development-only behavior and will not affect production.

Using the useRef hook is as simple as that. Now, we can see that the useRef returns a simple javascript object with a current property.

Dive Deeper into useRef Hook

Now that you understand the in’s and out’s of useRef, Let’s understand it more better.

The current property within the countRef object is mutable, meaning we can modify its value. To demonstrate this, let's create a button and a function that increments the current property's value by 1.

Start by creating a function like this inside your functional component.

const handleClick = ()=>{
    countRef.current = countRef.current + 1;
}

Here, we are just accessing the countRef’s current property by dot notation and incrementing it. We also need a button that will call this function when we click it.

<button onClick={handleClick}> Click Me! </button>

Now, we can show it's value inside a paragraph tag

<p> Count Ref Value: {countRef.current} </p>

After making the above changes your code now should look like below.

import React, { useRef } from 'react';
export default function App(){
    const countRef = useRef(0);
    console.log(countRef);

    const handleClick = ()=>{
        countRef.current = countRef.current + 1;
    }

    return (
        <div>
            <h2>Hello World</h2>
            <p> Count Ref Value: {countRef.current} </p>
            <button onClick={handleClick}> Click Me! </button>
        </div>
    );
}

Now, click the button and see if you can see the value incrementing.

It's not incrementing. Right?

If it’s not, then don’t worry there is nothing wrong with your code. It is expected behavior of useRef hook. As I mentioned from the beginning, the changes to the useRef values will not cause re-rending of that component.

When you change the ref.current property, React does not re-render your component. React is not aware of when you change it because a ref is a plain JavaScript object.

But you might be wondering, how do we know that the value is incrementing?

To check that let's make a small change in the handleClick function.

const handleClick = ()=>{
    countRef.current = countRef.current + 1;
    console.log(countRef.current)
}

Now, every time we click the button, it will console the value. Make the above change, click the button, and check the console.

In the console:

Hence, we proved that the current property value of useRef is changing and changing the value will not re-render the component.

Changing a ref does not trigger a re-render, so refs are not appropriate for storing information you want to display on the screen. Use state for that instead.

Example: Create a Timer

Let’s see a proper example of using useRef. For that, we can make a simple timer, which we can start, pause and reset.

Here we need to show some show value on the screen and need to change it every second. So, we will use useState Hook to store the information that we need to display on the screen.

 const [time, setTime] = useState(0);

Now, we have a time state that we can display on the screen and it is initialized to 0.

<p>{time} seconds</p>

Add the above paragraph tag to the JSX part of the component to display it.

Now, we have a state and we canto display it on screen. It's time to start the timer. For that, we can create a function like below.

const increment = () => {
    setTime((prev) => prev + 1);
};
const handleStart = () => {
    intervalRef.current = setInterval(increment, 1000);
};

Here, we are using the setInterval function which will call the increment function every 1000ms which means every second (more about setInterval is here). Every time it calls the increment function the state will be incremented by 1.

Now, we create a button and call the handleStart function on click.

<button onClick={handleStart}>Start</button>

Now, we can start the timer. It's time to pause and reset it.To pause the timer we need to stop the setInterval function using clearInterval. The setInterval function returns an id which we can use later in clearInterval function to stop. And, this is where we are going to use the useRef hook (To store the Id returned from the setInterval function).

Start by instantiating a useRef hook.

const intervalRef = useRef(null);

Here I instantiated it with a null which we will change later. Now, we have to update the handleStart function as below to store the Id returned by the setInterval function.

const handleStart = () => {
    intervalRef.current = setInterval(increment, 1000);
};

Here, we can see that we updated the current property of the intervalRef with Id returned by the setInterval function. With that being done, Its easy to pause and reset the timer.

const handlePause = () => {
    clearInterval(intervalRef.current);
};
const handleReset = () => {
    setTime(0);
};

Here, the handlePause function will clear the interval based on the value stored in intervalRef.current which we stored earlier and handleReset function will simply update the state to 0. Now, just create two more buttons and call those functions on click.

<button onClick={handlePause}>Pause</button>
<button onClick={handleReset}>Reset</button>

After doing the above changes, the code will look like below.

import React, { useRef, useState } from 'react';
export default function App() {
  const [time, setTime] = useState(0);
  const intervalRef = useRef(null);
  const increment = () => {
    setTime((prev) => prev + 1);
  };
  const handleStart = () => {
    intervalRef.current = setInterval(increment, 1000);
  };
  const handlePause = () => {
    clearInterval(intervalRef.current);
  };
  const handleReset = () => {
    setTime(0);
  };

  return (
    <div>
      <p>{time} seconds</p>

      <button onClick={handleStart}>Start</button>
      <button onClick={handlePause}>Pause</button>
      <button onClick={handleReset}>Reset</button>
    </div>
  );
}

The output will be:

Now, we can start, pause, and reset our timer. So, as we can see we have used the useRef function to store information that doesn’t need to be rendered on the screen. This might not make much difference here but, in large projects in the long run it will serve you well.

Accessing DOM element using useRef

The primary use case for useRef shines when we need to access DOM elements. Let's dive into this concept with a straightforward example. In this example, we'll access an image element from the DOM and, upon clicking a button, smoothly scroll it into view. It's a simple yet powerful demonstration of useRef's capabilities.

Let's Begin!

Start by creating three div’s with the class name container.

export default function App() {

  return (
    <div>
      <div className="container"></div>
      <div className="container"></div>
      <div className="container"></div>
    </div>
  );
}

Now add below css in style.css just to make them big and to add some background color.

* {
  box-sizing: border-box;
  margin: 0px;
  padding: 0px;
  scroll-behavior: smooth;
}
body{
  padding: 10px;
}
.container {
  height: 100vh;
  width: 100%;
  background-color: lightgray;
  margin-bottom: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
}

After adding the above css you can see three big boxes with the background color of light gray. Now add a button in the first container and an image in the third container just like below.

export default function App() {

  return (
    <div>
      <div className="container">
         <button>Show Me Image</button>
      </div>
      <div className="container"></div>
      <div className="container">
        <img src={'https://picsum.photos/300/300'} alt="Some random image" />
      </div>
    </div>
  );
}

Our goal here is to achieve smooth scrolling of an image when we click a button. To make any element on the website scroll into view, we can use the scrollIntoView() method (learn more here). The first step is to access the specific DOM element we want to scroll and then call the scrollIntoView method on it. And this is precisely where our trusty useRef hook comes into play.

Instantiate a useRef hook with a null value.

const imgRef = useRef(null);

Then provide this variable to the ref attribute of the element which you want to access. Just like below.

<img 
  ref={imgRef} 
  src={'https://picsum.photos/300/300'} 
  alt="Some random image" 
 />

From here on, React takes the reins and performs its magic. When React encounters the ref attribute, it automatically assigns the DOM node of the corresponding element to the current property of the imgRef object. This enables us to access the node and invoke methods on it directly through the ref object. For example, you can smoothly scroll the image into view with a simple call like this: imgRef.current.scrollIntoView(). Remember, React handles the task of setting the imgRef.current value to the actual DOM node.

Now that we can access the node let’s create a function that will handle the click.

const handleClick = () => {
    imgRef.current.scrollIntoView();
};

Additionally, you can utilize the imgRef.current to call any other methods or modify the properties of the associated node effortlessly. It grants you direct access and control over the DOM element, making it versatile and powerful for various interactions and manipulations.

Now, let's call this function when we click the button.

<button onClick={handleClick}>Show Me Image</button>

After making the above changes, your code should look like below.

import './style.css';
import React, { useRef } from 'react';

export default function App() {
  const imgRef = useRef(null);
  const handleClick = () => {
    imgRef.current.scrollIntoView();
  };
  return (
    <div>
      <div className="container">
         <button onClick={handleClick}>Show Me Image</button>
      </div>
      <div className="container"></div>
      <div className="container">
        <img 
          ref={imgRef} 
          src={'https://picsum.photos/300/300'} 
          alt="Some random image" 
          />
      </div>
    </div>
  );
}

With that being done, click on the button and the image will scroll into the view. Just like that, we can access the node from the DOM and manipulate it. There is much more than that you can do with it.

Using useRef on Custom Components

Let’s say in the above case instead of the <img/> tag you created a custom component for Images like below

function Images(props){
    return (
        <div>
          <img ref={ref} src={props.imgUrl} alt="Some random image" />
          <p>
            Caption: <i>{props.caption}</i>
          </p>
        </div>
    );
}

In this component, you can input both the URL and a caption via props. If you're not familiar with props, think of them as placeholders that allow us to make components reusable by substituting different values wherever needed. They're like dynamic slots for information in our components.

Now, replace the <img/> tag with this new component like below

<div className="container">
    <Image
       ref={imgRef}
       imgUrl={'https://picsum.photos/300/300'}
       caption={'Some Random Image'}
     />
</div>

Now the output will look something like below

If I want, I can reuse the same component just by changing the imgUrl and caption attributes of our component.

Let’s try to apply the same functionality of scrolling to the image when we click on the button. We already have the button and handleClick function let’s just add the ref attribute to the component and click the button.

Now your code should look like below

import './style.css';
import React, { useRef } from 'react';
export default function App() {
  const imgRef = useRef(null);
  const handleClick = () => {
    imgRef.current.scrollIntoView();
  };
  return (
    <div>
      <div className="container">
        <button onClick={handleClick}>Show Me Image</button>
      </div>
      <div className="container"></div>
      <div className="container">
        <Image
          ref={imgRef}
          imgUrl={'https://picsum.photos/300/300'}
          caption={'Some Random Image'}
        />
      </div>
    </div>
  );
}

function Images(props){
    return (
        <div>
          <img ref={ref} src={props.imgUrl} alt="Some random image" />
          <p>
            Caption: <i>{props.caption}</i>
          </p>
        </div>
    );
}

When you click on the button, you might see an error like this.

That means, the imgRef.current value is null and it is not able to read the properties of null. So, react is not able to automatically assign the node value to current property of the ref object, which React does like magic, hasn't taken place here.

This is a common and expected behavior. When we create our own components, they don't automatically pass the ref attribute to the DOM nodes within them. To address this, we have a handy solution called forwardRef. By wrapping our component in the forwardRef function as shown below, we can overcome this issue:

import {forwardRef} from 'react';
const Image = forwardRef(function (props, ref) {
  return (
    <div>
      <img ref={ref} src={props.imgUrl} alt="Some random image" />
      <p>
        Caption: <i>{props.caption}</i>
      </p>
    </div>
  );
});

In our component, the first parameter remains props as before, but now we also have access to another parameter, ref, which we can pass to any child nodes within the component. So, let's revisit what we did earlier – assigning the ref attribute of a node to the useRef object. This time, React will perform its magic as expected.

After the above changes the code should look like this:

import './style.css';
import React, { useRef, forwardRef } from 'react';
export default function App() {
  const imgRef = useRef(null);
  const handleClick = () => {
    imgRef.current.scrollIntoView();
  };
  return (
    <div>
      <div className="container">
        <button onClick={handleClick}>Show Me Image</button>
      </div>
      <div className="container"></div>
      <div className="container">
        <Image
          ref={imgRef}
          imgUrl={'https://picsum.photos/300/300'}
          caption={'Some Random Image'}
        />
      </div>
    </div>
  );
}

const Image = forwardRef(function (props, ref) {
  return (
    <div>
      <img ref={ref} src={props.imgUrl} alt="Some random image" />
      <p>
        Caption: <i>{props.caption}</i>
      </p>
    </div>
  );
});

Now try to click on the button again and it will scroll to the image. That’s it, with that we have also seen how we can pass the useRef object to our Custom components.

Key Takeaways

By using a ref, you ensure that:

  • You can store information between re-renders (unlike regular variables, which reset on every render)

  • Changing it does not trigger a re-render (unlike state variables, which trigger a re-render)

  • The information is local to each copy of your component (unlike the variables outside, which are shared)

Conclusion

  • You've learned the basics, including ref creation and usage in functional components.

  • Explored advanced applications like DOM element access and custom components.

  • Remember that learning in React is an ongoing process; keep experimenting and exploring.

  • Armed with useRef, you're well-prepared to enhance your React applications.

  • Your React journey is just beginning, and there are endless possibilities to explore.

Thanks for Reading

If you've made it this far, I truly appreciate you taking the time to read my blog. I hope you found this guide insightful and practical. If you have any questions, feedback, or topics you'd like us to cover in future blog posts, please feel free to reach out. We're here to support your learning journey.

Stay curious, keep coding 🚀👩‍💻

Bye People! See you in the next blog post...

References