DEV Community

Cover image for 🧠Understanding useRef in React
Rishabh Joshi
Rishabh Joshi

Posted on

4

🧠Understanding useRef in React

useRef is a React Hook that allows you to create a mutable reference object. This reference object can hold a value that persists across renders and changing its .current value does NOT trigger a re-render. It's also commonly used to get direct access to DOM elements.


What is useRef?

useRef is a React hook returning a mutable ref object with a single .current property.

const myRef = useRef(initialValue);
// myRef looks like: { current: initialValue }
Enter fullscreen mode Exit fullscreen mode
  • Initialization: The initialValue you provide is used to set myRef.current only during the component's initial render. It's ignored on subsequent renders. If you omit the argument, initialValue defaults to undefined.

  • Return Value: useRef returns the same ref object on every render of a given component instance. This is how it persists across renders.


🗝️Key Characteristics:

  • Mutable .current Property:
    You directly modify the ref.current property (e.g., myRef.current = newValue), unlike state variables from useState (which you update via a setter function),

  • No Re-render on Change:
    Assigning a new value to ref.current does not trigger a component re-render. This is the most significant difference from useState.

  • Persistence Across Renders:
    The ref object itself (and the value stored in ref.current) persists for the full lifetime of the component. React ensures you get the same ref object back on every render.


⚠️When Should You Mutate ref.current?

Since mutating ref.current doesn't cause a re-render, it's important when you perform the mutation.

  • ✅Safe: Inside event handlers (like onClick, onChange) or inside useEffect hooks. These run after the render cycle is complete or as a result of user interaction, making mutations predictable.

  • ❌Avoid (Generally): Mutating ref.current directly during the rendering phase (i.e., right inside the main body of your functional component). While React won't stop you, reading a ref during render is fine, but writing to it can make component behavior less predictable. This is because the component body should ideally be free of side effects, and mutations might not behave consistently depending on React's rendering optimizations (like Concurrent Mode). If you need to calculate something based on render and store it mutably, useEffect is usually the better place.


🔧Common Use Cases

useRef serves two primary purposes in React components:

1. Accessing DOM Nodes

This is a very common use case. useRef allows you to get a direct reference to a specific DOM element rendered by your component.

  • How it works:

    • Create a ref, usually initialized to null: const myInputRef = useRef(null);
    • Attach this ref to a JSX element using the special ref prop: <input ref={myInputRef} />.
    • After the component mounts and React creates the DOM node for the <input>, React will set myInputRef.current to that DOM node instance.
  • Why it's needed:
    For interacting directly with the DOM, which is sometimes necessary for:

    • Managing focus (e.g.- myInputRef.current.focus()).
    • Measuring element dimensions or position.
    • Triggering imperative animations.
    • Integrating with third-party libraries that need direct DOM access.
import React, { useRef, useEffect } from 'react';

function TextInputWithFocusButton() {
  // 1. Create a ref to hold the input DOM element
  const inputEl = useRef(null);

  // Example: Focus on initial mount 
  useEffect(() => {
    inputEl.current?.focus();     
  }, []);                 
  // Empty dependency array means run once after initial mount

  return (
    <>
      {/* 2. Attach the ref to the input element */}
      <input ref={inputEl} type="text" />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

2. Storing Mutable Values (Like Instance Variables)

You can use useRef to keep track of any mutable value that needs to persist across renders but should not cause a re-render when it changes.

  • Storing previous state or prop values.
  • Managing timer IDs (e.g., from setTimeout or setInterval).
  • Keeping track of internal component state that doesn't directly affect the rendered output.

  • How it works:

    • Initialization: You call useRef(initialValue) during your component's setup. React creates a plain JavaScript object like { current: initialValue }.
    • Persistence: React internally holds onto this specific object for the entire lifetime of your component instance. On every subsequent render, the exact same object is returned by the useRef hook.
    • Accessing: You can read the stored value anytime using yourRef.current.
    • Updating: You can change the stored value by directly assigning a new value to the .current property (e.g., yourRef.current = newValue).

⚠️No Re-render: this assignment to .current is just a standard JavaScript object property mutation. React does not monitor this change, and therefore, it does not trigger a component re-render. The value is updated silently within the persistent ref object.


The Analogy: The Room, The Closet, and The Label Maker

Imagine your React component is like a room. Every time the component "re-renders" (because its state or props change), it's like the room gets quickly dismantled and rebuilt with the updated information.

  • useState: Think of useState as the visible furniture or decorations in the room (like a couch or a painting). When you change something using the set function (e.g., changing the couch color), React notices this visible change and rebuilds the room (re-renders the component) to show the update. Everyone sees the new couch color.

  • useRef: Now, useRef gives you access to persistent tools that don't cause a room rebuild when used or changed. It primarily provides two kinds of tools:

The Hidden Storage Closet (for Mutable Values):

  • Calling useRef(initialValue) can create a small, hidden storage closet inside the room, with an initial item placed on its single shelf, labelled .current.
  • This closet (ref object) and its contents persist across room rebuilds (re-renders). Even when the room is dismantled and rebuilt, the closet remains exactly where it is, untouched.
  • You can open the closet anytime (preferably in effects or handlers) and change the item on the .current shelf (myRef.current = newValue).
  • Crucially: Changing the item inside the hidden closet does not cause the room to be rebuilt (it does not trigger a re-render). It's a silent change, hidden from the main rendering process.

The Persistent Label Maker (for DOM Access):

  • Calling useRef(null) can also give you a persistent label maker.
  • When you build the room, you can use the special ref prop on a specific part of the room's structure (like a window, a door, or a light switch – representing a DOM element like <input ref={myRef} />). This tells React: "Use the myRef label maker to put a unique, persistent label on this specific window when you build it."
  • After the room is built (the component mounts and renders the element), the label maker's display (myRef.current) now points directly to that specific, labeled window (the actual DOM node).
  • You can then use this reference (myRef.current) to interact directly with that specific part of the room (e.g., myRef.current.focus() to look through the window, or myRef.current.style.display = 'none' to cover it) without needing to rebuild the entire room (no re-render is triggered by accessing or manipulating the element via the ref).

In essence, useRef provides ways to either hold onto information silently (closet) or get a direct, persistent handle (label maker) to parts of the rendered output, all without interfering with React's normal state-driven rendering cycle.


🔄 useState vs useRef – Quick Comparison

Feature useState useRef
Triggers re-render on change? ✅ Yes ❌ No
Persists across renders? ✅ Yes ✅ Yes
Used for DOM access? ❌ No ✅ Yes
Ideal for UI-related state that affects rendering Mutable values or DOM refs that don’t affect UI
How to update value Using setter function (e.g., setValue()) Direct mutation (myRef.current = newValue)
Can be used in render phase? ✅ Yes (safe) ⚠️ Reading: Yes / Writing: Avoid in render phase

Wrapping Up

useRef is a powerful tool in your React toolkit, essential for interacting with the DOM and managing persistent mutable values that shouldn't trigger re-renders. Understanding when and why to use it over useState is key to writing efficient and effective React components.

when and why to use useRef:

  • You need direct access to a DOM element for imperative actions like focusing, measuring, or integrating with external libraries. useState cannot do this.
  • You need to store mutable data that persists across renders, but changing this data should NOT cause a re-render. This is useful for performance or specific logic needs.
  • Think: Timer IDs (setTimeout/setInterval), tracking previous state/props, flags not directly rendered.

In short: useState is for state that drives your UI updates. useRef is for references to DOM nodes or for holding mutable data silently behind the scenes.


Likes, Comments and feedback are welcome!😅

Happy coding!

Top comments (1)

Collapse
 
kevin_basiliovillanueva_ profile image
Kevin Basilio Villanueva

It helped me, thank you!