React useRef Hook

React useRef Hook

The useRef Hook allows you to persist values between renders. Unlike useState, updating a useRef variable does not trigger a re-render of your component.

It is primarily used for two distinct purposes:

  1. Accessing DOM elements directly.
  2. Storing mutable values that do not require re-rendering when updated.

1. Accessing DOM Elements

In vanilla JavaScript, you often use document.getElementById() to select and manipulate an HTML element. In React, directly manipulating the DOM is discouraged because React uses a Virtual DOM.

However, there are valid cases where you must access the actual DOM element, such as managing focus, text selection, or integrating with third-party DOM libraries (like charts). The useRef hook allows you to create a reference to an element safely.

Example: Focusing an Input

import React, { useRef } from 'react';

function FocusInput() { // 1. Create a ref with an initial value of null const inputRef = useRef(null);

const handleFocus = () => { // 3. Access the DOM element via the .current property inputRef.current.focus(); };

return ( <div> {/* 2. Attach the ref to the React element */} <input type="text" ref={inputRef} placeholder="Click button to focus me" /> <button onClick={handleFocus}>Focus the Input</button> </div> ); }

Notice that useRef returns an object with a single property called current. When we pass the ref to the ref= attribute of the <input>, React assigns the actual DOM node to inputRef.current.


2. Tracking Mutable Values Without Re-rendering

Sometimes you need to keep track of a value across renders, but changing that value shouldn't cause the screen to update.

If you use useState, every update triggers a render cycle. If you use a regular JavaScript variable (let count = 0), the variable gets reset back to 0 every time the component re-renders.

useRef perfectly solves this by surviving renders without triggering them.

Example: Counting Renders Without Infinite Loops

import React, { useState, useEffect, useRef } from 'react';

function RenderCounter() { const [inputValue, setInputValue] = useState("");

// Create a ref to track renders const renderCount = useRef(1);

useEffect(() => { // Update the ref value. This will NOT trigger another render! renderCount.current = renderCount.current + 1; });

return ( <div> <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> <h2>Render Count: {renderCount.current}</h2> </div> ); }

If we had used useState for renderCount in the useEffect above, updating the state would trigger a re-render, which would trigger the effect again, which would trigger a re-render, leading to a fatal infinite loop. useRef prevents this entirely.


Keeping Track of Previous State

Another common pattern for useRef is capturing what a state value used to be before the current render.

Because useEffect runs after the render is committed to the screen, we can update a ref with the current state inside an effect. The ref will always hold the "previous" value on the next render.

import React, { useState, useEffect, useRef } from 'react';

function TrackPrevious() { const [name, setName] = useState("John"); const previousName = useRef("");

useEffect(() => { previousName.current = name; }, [name]);

return ( <div> <input value={name} onChange={e =>


Rule of Thumb: Use useState when you want the UI to update visually when the data changes. Use useRef when you need to store data behind the scenes without the user interface needing to redraw.


Exercise

?

How do you read or modify the value stored inside a ref created by useRef?