Table of contents
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 useuseRef
to focus on an input field when a modal opens, ensuring a seamless user experienceIn 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 initialValue
that 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
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...