DEV Community

Mohsen Fallahnejad
Mohsen Fallahnejad

Posted on

React useRef vs useState — When to Use Which

React gives us useState and useRef to store values across renders. But they behave differently.


1. useState

  • Stores data that affects rendering.
  • When state changes → component re-renders.
  • Best for values you want to show in the UI.
function Counter() {
  const [count, setCount] = useState(0) // 🔄 triggers re-render
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

Every click updates count → React re-renders → UI shows new value.


2. useRef

  • Stores a mutable value that survives re-renders.
  • Updating .current does NOT trigger re-render.
  • Best for storing DOM nodes or values that don’t affect UI.
function Timer() {
  const intervalRef = useRef<number | null>(null) // 🗃️ stable across renders

  function start() {
    intervalRef.current = window.setInterval(() => {
      console.log("tick")
    }, 1000)
  }

  function stop() {
    if (intervalRef.current) clearInterval(intervalRef.current)
  }

  return (
    <>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

Here, intervalRef keeps the timer ID but doesn’t cause re-renders.


3. When to Use Which?

Use useState when…

  • The value affects rendering.
  • UI should update when value changes.
  • Examples: counters, form fields, theme toggles, fetched data.

Use useRef when…

  • The value should persist across renders but doesn’t affect UI.
  • You want to avoid unnecessary re-renders.
  • Examples:
    • Accessing DOM elements (ref={myRef})
    • Storing timer IDs, WebSocket connections
    • Holding previous values for comparison

4. Example: Using Both Together

function InputFocus() {
  const [text, setText] = useState("")       // shows in UI
  const inputRef = useRef<HTMLInputElement>(null) // reference to DOM

  return (
    <>
      <input ref={inputRef} value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => inputRef.current?.focus()}>
        Focus input
      </button>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode
  • useState stores the text (triggers render).
  • useRef stores a DOM reference (no render needed).

5. Quick Mental Model 🧠

  • useState = for UI → change → re-render.
  • useRef = for storage → change → no re-render.

6. Cheatsheet Table

Feature useState useRef
Triggers re-render on change ✔️ Yes ❌ No
Best for UI values (text, count, toggles) DOM refs, timers, storage
Persists across renders ✔️ Yes ✔️ Yes
Mutable without causing render ❌ No ✔️ Yes
Common uses Form inputs, counters, fetched data Focus input, store IDs, previous values, WebSocket refs

⚡ That’s it! Now you know when to reach for useState and when to grab useRef — and have a cheatsheet for quick reference.

Originally published on: Bitlyst

Top comments (0)