useRef vs useState – When to Use What in React?


useRef vs useState – When to Use What in React

React provides a set of powerful Hooks that enable developers to manage state, side effects, and application behavior inside functional components. Among them, useState and useRef are foundational tools used in nearly every React application.

While both Hooks allow you to store data across renders, they serve very different purposes. Understanding when to use useRef instead of useState, and vice versa, is critical for building efficient, maintainable components.

In this guide, we’ll explore the differences between useRef and useState, their internal behavior, practical use cases, and how to determine which one fits best depending on your needs.

What Is useState?

useState is a Hook that allows you to introduce reactive state into a functional component. When the state changes using the provided setter function, React automatically re-renders the component so that the UI reflects the latest state.

Example:

import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

In this example:

  • count holds the current state.
  • setCount updates the state.
  • Updating count causes a re-render.

What Is useRef?

useRef is a Hook that returns a mutable object which persists for the full lifetime of the component. It is primarily used for:

  • Accessing or modifying DOM elements directly.
  • Storing non-reactive mutable data.
  • Tracking values between renders without causing re-renders.

Example:

import React, { useRef } from 'react';

function TextInput() {
const inputRef = useRef(null);

const handleFocus = () => {
inputRef.current.focus();
};

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

Here, inputRef.current holds the reference to the DOM input element. Changing this value will not trigger a re-render.

Key Differences Between useState and useRef

FeatureuseStateuseRef
Triggers component re-render on updateYesNo
Value persists across re-rendersYesYes
Designed for UI stateYesNo
Ideal for storing mutable valuesNoYes
Can reference DOM elementsNoYes
Access methodSetter function.current property
ReactiveYesNo

When Should You Use useState?

1. Managing Values That Affect Rendering

If the variable is used in JSX or determines what gets rendered, use useState.

const [isVisible, setIsVisible] = useState(false);

This updates the DOM to show or hide content based on the value.

2. Tracking Controlled Input Fields

For controlled forms, where input fields are tied to component state:const [username, setUsername] = useState('');
<input value={username} onChange={(e) => setUsername(e.target.value)} />

3. Implementing UI Logic and Conditional Rendering

Use useState for toggles, modals, dynamic classes, or conditional elements.

{isDarkMode ? <DarkTheme /> : <LightTheme />}

When Should You Use useRef?

1. Accessing and Manipulating DOM Elements

useRef is ideal for working with DOM APIs in a declarative React environment.

const inputRef = useRef(null);

useEffect(() => {
inputRef.current.focus();
}, []);

2. Persisting Mutable Data That Does Not Affect Rendering

If you need to persist data without re-rendering (e.g., timer IDs, scroll positions, cached data), useRef is the correct choice.

const timeoutId = useRef(null);

3. Tracking Render Counts or Previous Values

useRef allows you to keep values between renders without reacting to them.const renderCount = useRef(1);
useEffect(() => {
renderCount.current += 1;
});

4. Avoiding Unnecessary Re-renders

If you are updating a value that does not impact the visual output of the component, use useRef to avoid performance overhead.

const debounceTimer = useRef(null);

Common Mistakes to Avoid

Mistake: Using useRef for Values That Control the UI

const inputValue = useRef('');
<input value={inputValue.current} />

Why it’s wrong: Updating .current does not trigger a re-render, so the input will not behave as expected.

Correct approach:

const [inputValue, setInputValue] = useState('');

Mistake: Using useState for Data That Doesn’t Impact the UI

const [timerId, setTimerId] = useState(null);

This is inefficient if the timer ID is not used in rendering.

Better approach:

const timerId = useRef(null);

How to Choose Between useState and useRef

Ask yourself the following:

  1. Does the value control what appears in the UI?
    → Yes → Use useState
  2. Is the value only used internally (e.g., a reference, cache, timer)?
    → Yes → Use useRef
  3. Will updating the value require a re-render for UI changes to be visible?
    → Yes → Use useState
  4. Do you need to manipulate a DOM node directly?
    → Yes → Use useRef

Real-World Use Case Summary

ScenarioHook to UseExplanation
Toggling a modaluseStateThe modal is part of the UI
Tracking scroll positionuseRefValue is not rendered
Controlled form inputuseStateInput must reflect in real time
Debounce timer for search inputuseRefNo UI update required
Accessing a DOM input to focususeRefDirect DOM manipulation
Displaying loading/error statesuseStateAffects rendering of loading indicators

Final Thoughts

Both useState and useRef are indispensable tools in the React ecosystem, but they are designed for different responsibilities.

  • Use useState when your value needs to trigger a re-render and impact the UI.
  • Use useRef when you need to persist data across renders without triggering updates to the component’s output.

Mastering the correct usage of these two Hooks leads to better performance, more predictable behavior, and cleaner component logic. As your React applications grow, your ability to distinguish when to use useRef and useState will be a key part of writing scalable, maintainable code.


Leave a Comment

Your email address will not be published. Required fields are marked *