Back to list
ReactHookuseRefuseStateDOM

[React][Hook] Store Values Without Re-renders and Control the DOM with useRef

Use useRef when you need a value that survives re-renders without updating the UI, or when you need a React-friendly way to focus and control DOM nodes.

1. Environment

  • OS: Windows 11 / macOS
  • Frontend library: React.js (v18+)
  • Core concept: React Hooks (useRef, useState)
  • IDE: Visual Studio Code (VS Code)

2. Problem

When a component needs to remember data, useState is usually the first tool that comes to mind. But useState always re-renders the screen when the value changes.

So what do you do when you must keep a value but do not need a UI update? And in React, how do you reach a specific DOM node the way vanilla JS uses document.getElementById()?

3. Cause

Suppose you only need to track “how many times the button was pressed|–internally, or run a background timer that ticks every second. If you store that in useState, the whole component re-renders on every increment - even when nothing on screen depends on that number.

Those useless re-renders hurt performance. You need a way to update a value quietly without touching the UI.

4. Solution

1) Store a value without re-rendering (useState vs useRef)

This example shows the core difference:

  • Increase Ref raises the console value but the UI stays the same.
  • Later, Increase State re-renders the screen, and the accumulated Ref value appears in one shot.
import React, { useState, useRef } from 'react';

function Counter() {
  const [stateCount, setStateCount] = useState(0);

  // 1. Create a ref object with initial value 0
  const refCount = useRef(0);

  const increaseState = () => {
    setStateCount(stateCount + 1);
    console.log('State updated! The screen re-renders immediately.');
  };

  const increaseRef = () => {
    // Always read/write through .current
    refCount.current = refCount.current + 1;
    console.log('Ref updated! Value rose, UI did not:', refCount.current);
  };

  return (
    <div>
      <p>State count: {stateCount}</p>
      <p>Ref count: {refCount.current}</p>
      {/* Every click updates the number on screen */}
      <button onClick={increaseState}>Increase State</button>
      {/* UI stays put; the value still accumulates inside */}
      <button onClick={increaseRef}>Increase Ref</button>
    </div>
  );
}

Important: Do not write refCount = refCount + 1. useRef(0) does not return the number 0“it returns an object shaped like { current: 0 }. Read and update the real value only through .current.

2) Access a DOM node (auto focus)

When a “Write post|–button should put the cursor in the title field, attach a ref to the input and call focus():

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

function AutoFocusInput() {
  // 1. Create a ref (initial value null)
  const inputRef = useRef(null);

  useEffect(() => {
    // 3. After paint, inputRef.current points at that <input>
    // Same idea as document.getElementById(...).focus() in vanilla JS
    inputRef.current.focus();
  }, []);

  return (
    <div>
      {/* 2. Wire the ref prop to the DOM node you need */}
      <input ref={inputRef} type="text" placeholder="Auto-focused here!" />
      <button onClick={() => inputRef.current.focus()}>Focus again</button>
    </div>
  );
}

5. Summary

Component variables compared: useState, useRef, and a plain let:

Re-renders on change Survives re-render Typical use
useState Yes Yes Values that must show on screen
useRef No Yes Hidden internal data; DOM access
Plain let No No (resets) Temporary values fine to reset each render

Related posts