Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use useState when changing a value should update what React renders. Use useRef when a value must persist between renders but changing it should not trigger a render—most often for a DOM node, timer ID, or imperative handle.

Both Hooks preserve information across renders; the difference is whether that information belongs to React’s reactive rendering flow. The deciding question is: if this value changes, should the user see something different?

The one-question test

Choose the Hook by the value’s role, not by whether it is a number, string, object, or function. State is for rendered data React should respond to. A ref is a persistent mutable container for data React does not need to track for rendering.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Question useState useRef
What does it return? A pair: current value and setter An object with a current property
Does changing it schedule a render? Calling the setter schedules an update; React may skip rendering when the next value is identical No
How is it changed? Through the setter; treat the value as a snapshot for that render Assign directly to ref.current
Typical role Form values, open/closed state, errors, selected items DOM nodes, timer IDs, external instances, non-UI mutable data

React’s guide to referencing values with refs describes refs as an escape hatch for values not needed for rendering. If a value determines JSX, use state or another reactive source—not a ref simply to avoid rendering.

How useState works

useState returns the state value for the current render and a setter that requests an update:

const [count, setCount] = useState(0);

The variable count is not changed in place by calling setCount. Each render sees its own state snapshot. The setter schedules React to process the requested next state, and React renders the relevant component tree as needed. If the next state is identical to the current one under Object.is, React may skip the render as an optimization. See the useState reference.

Use an updater when the next value depends on the previous one

Two calls based on the same render snapshot can request the same result:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
function handleClick() {
  setCount(count + 1);
  setCount(count + 1);
}

When updates depend on pending state, pass an updater function. React applies each updater to the pending value:

function handleClick() {
  setCount(value => value + 1);
  setCount(value => value + 1);
}

This is also the appropriate pattern for a single increment when the handler should calculate from the latest pending count.

Treat state objects as values, not mutable containers

Do not mutate an object held in state and expect React to notice:

user.name = 'New name'; // Does not request a React update

Instead, provide a new object through the setter:

setUser(previousUser => ({
  ...previousUser,
  name: 'New name',
}));

JavaScript does not make state objects magically immutable; this is an application discipline that keeps updates observable and predictable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How useRef works

A call such as const valueRef = useRef(initialValue) gives the component a ref object with a current property. React returns the same object on subsequent renders, and code can assign to that property:

valueRef.current = nextValue;

The assignment changes the ordinary JavaScript object immediately, but React is not notified and does not schedule a render. A useful mental model—not a guarantee about React’s implementation—is a persistent box containing current. The useRef reference documents its identity, mutation, and rendering caveats.

Refs can hold any value, not just DOM elements: a timeout ID, a connection object, a widget instance, or a previous value. Use them when that data must be remembered but does not itself determine the rendered output.

State snapshots and mutable refs behave differently

After setCount(count + 1), reading count again in the same handler still gives the value from that render:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
function handleClick() {
  console.log(count); // Current render's snapshot
  setCount(count + 1);
  console.log(count); // Still that snapshot
}

A ref assignment, by contrast, changes the object’s property immediately:

function handleClick() {
  console.log(valueRef.current);
  valueRef.current += 1;
  console.log(valueRef.current); // The newly assigned value
}

That immediate mutation is useful for imperative code, but it does not update the screen. A ref can make a mutable current value available to a callback, but using one to sidestep React’s data flow can also hide changes the UI or an Effect needs to respond to.

Use state for values that shape the UI

Examples include a controlled field’s value, whether a menu is open, the selected tab, a loading indicator, or a visible validation message. The value belongs in the render model, so updating it should cause React to produce the corresponding UI.

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(value => value + 1)}>
      Clicked {count} times
    </button>
  );
}

If this counter used a ref instead, its click handler could increment countRef.current, but the text would remain unchanged until an unrelated render happened. That is a correctness bug, not a useful performance optimization.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use refs for DOM access and imperative handles

A DOM element is a common ref use because code sometimes needs to call a browser API such as focus() or scrollIntoView(). Create the ref with null, attach it through the JSX ref prop, and use it after React has attached the node—for example, in an event handler:

import { useRef } from 'react';

export default function SearchBox() {
  const inputRef = useRef(null);

  function focusInput() {
    inputRef.current?.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus input</button>
    </>
  );
}

Before attachment, or when a conditionally rendered element has been removed, inputRef.current may be null. React attaches the DOM node after committing it and can clear the ref when the node is removed. See Manipulating the DOM with Refs.

Timer IDs and other non-UI values

A timer ID needs to survive renders so a later event can cancel it, but displaying the ID ordinarily has no UI purpose. A ref can hold it without requesting a render:

import { useEffect, useRef } from 'react';

function SearchInput() {
  const timeoutRef = useRef(null);

  useEffect(() => {
    return () => clearTimeout(timeoutRef.current);
  }, []);

  function handleChange() {
    clearTimeout(timeoutRef.current);
    timeoutRef.current = setTimeout(() => {
      console.log('Searching...');
    }, 300);
  }

  return <input onChange={handleChange} />;
}

The same reasoning can apply to animation-frame IDs, media-player or third-party widget handles, and mutable integration objects. If the value’s change should update the UI, store the relevant UI state separately.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Remembering a previous value

A ref can record the prior prop or state value when a comparison is useful but that stored value does not independently control rendering. Updating the ref in an Effect means the render reads the value from before that Effect:

import { useEffect, useRef } from 'react';

function Example({ value }) {
  const previousValueRef = useRef();

  useEffect(() => {
    previousValueRef.current = value;
  }, [value]);

  const previousValue = previousValueRef.current;
  return <p>Current: {value}; previous: {previousValue ?? 'none'}</p>;
}

Do not assign the incoming value to the ref during render to implement this pattern; that changes the timing and conflicts with render purity.

Using state and a ref together

A component can use both Hooks for different jobs. In a controlled input, state supplies the displayed value, while a ref supplies imperative access to the element:

import { useRef, useState } from 'react';

function TextInput() {
  const [text, setText] = useState('');
  const inputRef = useRef(null);

  return (
    <>
      <input
        ref={inputRef}
        value={text}
        onChange={event => setText(event.target.value)}
      />
      <button onClick={() => inputRef.current?.focus()}>Focus</button>
    </>
  );
}

The choice is about each value’s role, not about picking one Hook for the whole component. See React’s overview of built-in Hooks.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Ref pitfalls that cause stale or unpredictable behavior

Do not use a ref as visible state

If a ref changes in a handler but JSX depends on it, the screen will not be notified. Use state for values that need to appear in JSX, drive conditional rendering, or otherwise update the interface.

Do not read or write ref.current during render

React expects rendering to be predictable. In general, read and mutate refs in event handlers, Effects, or imperative integrations—not while calculating JSX. React permits a narrow initialization pattern such as creating an expensive object once:

const playerRef = useRef(null);
if (playerRef.current === null) {
  playerRef.current = new VideoPlayer();
}

This exception is for predictable initialization, not a license to use refs as render-time storage. The reference guidance describes the limitation.

A ref is not a reactive Effect dependency

Changing ref.current does not render the component, so React has no new render in which to compare a dependency. Putting ref.current in an Effect dependency array does not make mutations reactive. If a change should trigger synchronization, use state, props, or an appropriate external-store subscription. React explains how Effects track reactive values.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Do not use a ref merely to suppress an Effect’s development behavior in Strict Mode. Make setup and cleanup correct instead; React’s Effect synchronization guidance addresses this pattern.

A ref does not prevent unrelated renders

A component can still render because its state, props, context, or parent changed. A ref only means that changing its current property does not itself schedule a render.

When neither Hook is the right answer

  • Ordinary local variable: Use one for a value needed only during the current render or function call.
  • Derived value: Calculate a value from props or existing state during rendering rather than storing a duplicate. For example, build a full name from first and last names. This avoids keeping redundant values synchronized; see React’s state guidance.
  • useReducer: Use it when related state transitions are clearer as actions handled by a reducer. It is still reactive state, not a ref replacement.
  • Props or context: Use props for parent-to-child data and context for values shared across a subtree; do not hide reactive data in refs to avoid normal updates.
  • External store: If an external source changes and React must update its subscribers, use a subscription mechanism such as useSyncExternalStore rather than expecting a ref to notify React.

A practical decision checklist

  1. Does the value affect the JSX or visible behavior? Use state, a reducer, props, context, or another reactive source.
  2. Can it be calculated from current props or state? Derive it during render instead of storing it redundantly.
  3. Does it need to survive another render? If not, a local variable may be enough.
  4. Does changing it need to notify React? If yes, use state or another reactive mechanism; if no, a ref may fit.
  5. Is it a DOM node or imperative handle? Use a ref, then access it at an appropriate time after attachment.

In short, state is persistent data that participates in React’s rendering model; a ref is persistent mutable data that React does not track for rendering. Choosing by that contract keeps both UI updates and imperative code predictable.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.