Have you ever needed to store a value in React without triggering a re-render? Or maybe you've tried to set focus to an input field or element programmatically but were unsure of the best approach? React's useRef hook could be your answer; it is maybe one of the most under-appreciated yet powerful utilities for handling DOM interaction, value storage, and performance optimization.
In the past, when React was initially released, developers were compelled to use ES6 class components and lifecycle methods—likely to create difficult, unmaintainable code. Afterward, React hooks arrived in version 16.8.0, which standardized functional components and made state management easier.
Among all the hooks, useRef has become a popular and go-to choice. While some hooks cause re-renders, useRef allows you to hold values that persist between renders without triggering them. It is also ideal for accessing and modifying DOM elements directly, which makes it a great option for managing focus, animations, and performance optimizations.
Whether you're using the useRef custom hook with TypeScript, calling it within the useEffect, or dynamically controlling focus, this tutorial is intended to illustrate the proper usage of useRef React hooks. We will cover how to use the useRef custom hook, comparision with createRef, and it real-world usage examples and best practices.
What is the useRef hook?
How does React useRef() work?
What is the createRef function?
Using refs to access DOM elements
Differences between useRef and createRef
Best practices when working with refs
Implementing useRef in real-world applications
Common pitfalls of useRef
Practical use cases of refs in React
Performance optimizations with useRef
When to use the React useRef hook
React's useRef hook is a built-in utility that gives access to store and retain values without inducing re-renders. While useState re-renders a component as soon as there is any alteration in state, useRef hook allows you to maintain a previous value, which is the same on subsequent renders. This capability makes it very helpful in storing references to DOM elements, observing current as well as past values, and enhancing performance as a whole.
When dealing with React components, most developers utilize state to handle changing data. Not all values, though, need to trigger re-renders. Some pieces of data like references to form inputs, timers, or past state values need to be kept static without affecting the component lifecycle. That is exactly where useRef comes in handy.
import { useRef } from "react";
const MyComponent = () => {
const ref = useRef(true);
const toggleValue = () => {
ref.current = !ref.current;
};
console.log(ref.current); // true
return <button onClick={toggleValue}>Toggle</button>;
};
Persists Data Across Renders - The current property maintains count value of its worth, preserving values continuing even when the part is being re-rendered.
Doesn't cause re-renders- unlike the case of useState, updating useRef.current does not cause the component to re-render.
Helpful to Mutable Values – Best to store changing values (e.g., timers, past state, or references to form inputs) without triggering a re-render.
Mostly Used for DOM Manipulation – Best suited to target input fields, monitor element sizes, or communicate with third-party libraries.
The hook React.useRef() returns a mutable ref object with the current value passed as an argument (or undefined if no argument is supplied) initialized in the.current property. When the.current property is updated, it enables you to work directly with a DOM element or store a changeable value that endures between re-renders without requiring a parent component re-render. Because of this, it can be used to control focus, animations, or cache values without changing how the component renders.
Here's how it works and its common use cases:
Core Functionality:
Creating a Ref Object:
JavaScript
import React, { useRef } from 'react';
function MyComponent() {
const myRef = useRef(null); // Initialize with null for a DOM element
const myValueRef = useRef(0); // Initialize with a number for a mutable value
// ... component logic ...
}
Calling useRef() creates a plain JavaScript object with a single property: .current.
Attaching to DOM Elements:
You can attach a ref object to a JSX element using the ref attribute:
JavaScript
import React, { useRef, useEffect } from 'react';
function MyInputComponent() {
const inputRef = useRef(null);
useEffect(() => {
// Access the actual DOM element after the component has mounted
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return <input ref={inputRef} type="text" />;
}
React sets the inputRef's.current property to the actual DOM element that corresponds to the tag when the component renders. This enables you to use common DOM APIs (such as inputRef.current.focus() and inputRef.current.value) to directly interact with the DOM element.
Holding Mutable Values:
Mutable values that don't cause a re-render when altered can also be stored using useRef(). This is helpful for the following:
JavaScript
import React, { useRef } from 'react';
function Timer() {
const intervalId = useRef(null);
let count = 0; // This will reset on every re-render
const startTimer = () => {
if (!intervalId.current) {
intervalId.current = setInterval(() => {
count++;
console.log('Count:', count);
// We don't want to re-render just because 'count' changed
}, 1000);
}
};
const stopTimer = () => {
if (intervalId.current) {
clearInterval(intervalId.current);
intervalId.current = null;
}
};
return (
<div>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</div>
);
}
The interval's ID is stored in intervalId.current in this example. The interval ID wouldn't cause a re-render, which is what we want, if we stored it in a regular state variable. For persistent mutable values, the count variable, which is declared directly in the component scope, will be reset upon each re-render, emphasizing the distinction between a regular variable and a ref.
Managing Focus, Text Selection, or Media Playback: Managing Focus, Text Selection, or Media Playback: Using DOM elements directly to accomplish tasks such as controlling video/audio playback, choosing text, or focusing on an input field.
Triggering Imperative Animations: Integrating with third-party animation libraries that require direct DOM manipulation.
Connecting to Third-Party DOM Libraries: Using libraries that have direct control over the DOM.
Storing Mutable Values That Don't Trigger Re-renders: Holding values like timers, animation IDs, or previous prop values without causing unnecessary re-renders.that don't cause needless re-renders
Less Frequently Used in Functional Components: In class components, refs may be used to retrieve the properties or methods of child component instances. This is less frequent with functional components, and context or prop drilling are frequently more effective ways to handle them.
Avoid Relying on Ref Values for Rendering Logic: You shouldn't use ref values to control what is rendered on the screen because they don't cause a re-render. Use state variables for that.
Accessing current in useEffect: When working with DOM elements, make sure to access ref.current within a useEffect hook (or a lifecycle method in class components) only after the component has mounted and the reference has been connected to the DOM element. Null could be the result of accessing it during the initial render.
Developers can create and manage references to instances of class components or DOM elements using React's createRef function. It offers a direct method of accessing a reference made by the rendered elements of a component without the need for state or props.
React assigns an object with a.current property that contains the reference to the target element's current property when an input element reference is created using createRef. The ref object's current property part of the input element itself can then be interacted with using this reference, for example, programmatically focusing an input field or determining the size of an element.
import { createRef, Component } from "react";
class MyComponent extends Component {
constructor(props) {
super(props);
this.ref = createRef();
}
toggleValue = () => {
this.ref.current = !this.ref.current;
};
render() {
console.log(this.ref.current);
return <button onClick={this.toggleValue}>Toggle</button>;
}
}
Both useRef and createRef are used to create references in React, but they differ in their usage and behavior. Understanding their distinctions is essential for choosing the right tool for your application.
Feature | useRef | createRef |
---|---|---|
Used in | Functional components | Class components (legacy) |
Takes an argument? | Yes, an initial value | No |
Returns same ref object on re-renders? | Yes | No |
Causes re-render when updated? | No | No |
import React, { useRef } from "react";
const FocusInput = () => {
const inputRef = useRef(null);
const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<input ref={inputRef} type="text" placeholder="Click button to focus" />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
};
export default FocusInput;
Here, useRef maintains a persistent reference to initial value in the input field without triggering re-renders when an updated value of initial value is.
import React, { Component, createRef } from "react";
class FocusInputClass extends Component {
constructor(props) {
super(props);
this.inputRef = createRef();
}
handleFocus = () => {
if (this.inputRef.current) {
this.inputRef.current.focus();
}
};
render() {
return (
<div>
<input ref={this.inputRef} type="text" placeholder="Click button to focus" />
<button onClick={this.handleFocus}>Focus Input</button>
</div>
);
}
}
export default FocusInputClass;
Every time the reference new ref object used in the component re-renders, a new reference and ref object are created using createRef. For static components, this works well, but in applications that are updated frequently, it becomes inefficient.
useRef is more efficient in functional components because it keeps the same reference across renders.
Although it generates a new reference with every re-render, createRef is helpful in class components.
When their.current value is changed, neither approach results in re-renders.
UseRef is typically a better choice for contemporary React development because of its effectiveness and hook compatibility.
Knowing these differnces will help you decide whether to use useRef or createRef depending on the requirements of your React project.
One of the most common uses of refs is accessing DOM elements directly. Instead of using document.querySelector or getElementById, we have function app can attach a ref to JSX elements and manipulate them as needed.
import { useRef, useEffect } from "react";
const MyComponent = () => {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} type="text" />;
};
When input element of the component mounts, current property of the input field is automatically focused using inputRef.current.focus().
Use refs only when necessary – Refs helps in interacting with DOM elements, which keep mutable values between renders, or managing external APIs. dont use them for general state management.
Do not use refs for state updates – use useState instead of useRef. Refs if a value needs to update the UI or trigger a re-render, do not cause re-renders when their values change.
Declare refs properly – UseRef should always be defined at the component's top level. It can produce unexpected behavior if used inside loops, conditions, or nested functions.
Clean up references as needed: To avoid memory leaks, make sure that references are properly cleaned inside useEffect if they are being used with event listeners, timers, or subscriptions.
Use forwardRef for passing refs to child components – If you need to pass a ref to a child component, use forwardRef to properly manage it without breaking component encapsulation.
Reality: useState triggers re-renders, whereas useRef does not.
import { useState, useRef } from "react";
const MyComponent = () => {
const [state, setState] = useState(0);
const ref = useRef(0);
const updateValues = () => {
setState(state + 1); // Triggers re-render
ref.current += 1; // Does NOT trigger re-render
};
return (
<div>
<p>State: {state}, Reference: {ref.current}</p>
<button onClick={updateValues}>Update</button>
</div>
);
};
Use useState when a change in current property should cause a re-render.
Use useRef when you need to persist values without affecting re-renders.
Reality: Updating ref.current does not cause the component to re-render.
This is useful when storing mutable values unlike state variables preserving values that don’t need count state variable to count and to be updated value trigger updates in state and other features of the UI.
We can use useRef to store previous state values without triggering extra re-renders:
import { useRef, useState, useEffect } from "react";
const MyComponent = () => {
const [count, setCount] = useState(0);
const prevCountRef = useRef(0);
useEffect(() => {
prevCountRef.current = count; // Stores previous count
}, [count]);
return (
<div>
<p>Current: {count}, Previous: {prevCountRef.current}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
useRef stores the ref object's current property value of the the ref attribute previous value of state without causing re-renders, unlike useState.
If a component requires an expensive computation that doesn’t need to re-run on each render, useRef can store the result:
const ExpensiveCalculation = () => {
const calcRef = useRef<number | null>(null);
if (calcRef.current === null) {
calcRef.current = Math.random() * 1000; // Expensive calculation
}
return <p>Result: {calcRef.current}</p>;
};
The calculation runs only once because useRef preserves the initial value, and prevents unnecessary recalculations.
A common use case for useRef is detecting clicks outside a popup to close it automatically.
Create a Hooks folder in your React project.
Inside it, create UseClickAway.ts.
Add this custom hook:
import { useEffect, useRef } from "react";
/**
* Detects clicks outside a referenced element and triggers a callback.
*/
const useClickAway = (callback: () => void) => {
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
callback(); // Close popup when clicking outside
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [callback]);
return ref;
};
export default useClickAway;
import { useState } from "react";
import useClickAway from "./Hooks/UseClickAway";
const Popup = () => {
const [isOpen, setIsOpen] = useState(false);
const popupRef = useClickAway(() => setIsOpen(false));
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Popup</button>
{isOpen && (
<div
ref={popupRef}
style={{
padding: 20,
background: "lightgray",
position: "absolute",
border: "1px solid black",
}}
>
Click outside to close me!
</div>
)}
</div>
);
};
export default Popup;
popupRef is assigned to the popup <div>.
When a user clicks outside the popup, useClickAway detects the event and closes the popup.
The useEffect hook automatically removes the event listener when the component unmounts.
When you need to persist values across renders without triggering a re-render.
When you need to directly interact with the DOM (e.g., focus input, track dimensions).
When managing timeouts, intervals, or previous state values efficiently.
When preventing unnecessary re-renders in performance-sensitive components.
React's useRef Hook is a crucial component for handling focus, storing mutable values, and controlling DOM interactions. Knowing the use cases for the React useRef hook guarantees improved performance and cleaner code, whether you're utilizing it in a standard project or in React TypeScript. Developers can efficiently create dynamic and interactive components by implementing the useRef hook in useEffect. To improve your React apps, start utilizing useRef React hooks right now!
Get expert React solutions! Hire React developers for projects and utilize useRef to manage the DOM, store values, and eliminate unwanted re-renders. Book a Free Call Today.
The useRef hook in React allows you to store values across renders without triggering re-renders. It is commonly used to reference DOM elements and manage values specific elements that don’t need to cause updates to the UI.
React provides many hooks, previous example, but four essential ones are:
useState – Manages local component state.
useEffect – Handles side effects like API calls and subscriptions.
useRef – Stores values and references without triggering re-renders.
useContext – Provides access to shared state without prop drilling.
useState triggers a re-render whenever the state value changes.
useRef does not trigger re-renders when its value (ref.current) is updated.
Use useState when you need changes to update the UI, and useRef when you need to persist values without affecting rendering.
The useRef hook is mainly used for: Interacting with DOM elements (e.g., focusing input fields, playing media), Storing the current value and previous values, to compare state changes between renders,Managing timers and intervals efficiently without re-renders.
This website uses cookies to analyze website traffic and optimize your website experience. By continuing, you agree to our use of cookies as described in our Privacy Policy.