How to Use useState in React

What you’ll build or solve

You’ll add state to a React component and update it correctly.

When this approach works best

Use this approach when you need to:

  • Track simple values like counters, toggles, or input text.
  • Update UI based on user interaction.
  • Store temporary UI state inside a component.

Avoid using local state for data that should be shared across many unrelated components. In those cases, lift state up or use a different state management pattern.

Prerequisites

  • A React app set up
  • Basic understanding of functions and JSX

Step-by-step instructions

1) Initialize state and update with a direct value

Import useState and call it at the top level of your component.

JavaScript

import {useState }from"react";

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

return (
<div>
<p>{count}</p>
<buttononClick={() =>setCount(5)}>Set to 5</button>
</div>
  );
}
  • useState(0) sets the initial value.
  • setCount(5) replaces the current state with a new value.

Use direct updates when the next value does not depend on the previous one.

What to look for:

  • Always call useState at the top level, not inside conditions or loops.
  • The setter replaces the previous value entirely.

2) Use the functional updater when depending on previous state

When the next state depends on the previous one, use the functional form.

JavaScript

import {useState }from"react";

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

functionincrement() {
setCount(prev =>prev+1);
  }

return (
<div>
<p>{count}</p>
<buttononClick={increment}>Increment</button>
</div>
  );
}

The function receives the latest state value as prev.

Use this pattern when:

  • Updating based on the previous value.
  • Triggering multiple updates in one event.
  • Avoiding stale values inside closures.

Examples you can copy

Example 1: Toggle visibility

JavaScript

import {useState }from"react";

exportdefaultfunctionToggle() {
const [open,setOpen]=useState(false);

return (
<div>
<buttononClick={() =>setOpen(prev =>!prev)}>
        Toggle
</button>

      {open&&<p>Now you see me</p>}
</div>
  );
}

The functional updater prevents incorrect toggling if updates happen quickly.


Example 2: Controlled input field

import {useState }from"react";

exportdefaultfunctionNameInput() {
const [name,setName]=useState("");

return (
<input
value={name}
onChange={e =>setName(e.target.value)}
placeholder="Type your name"
/>
  );
}

Each change replaces the previous state directly.


Example 3: Counter with multiple updates

JavaScript

import {useState }from"react";

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

functionincreaseTwice() {
setCount(prev =>prev+1);
setCount(prev =>prev+1);
  }

return (
<div>
<p>{count}</p>
<buttononClick={increaseTwice}>
        +2
</button>
</div>
  );
}

Using the functional updater guarantees both increments apply correctly.


Common mistakes and how to fix them

Mistake 1: Mutating objects or arrays directly

What you might do:

const [todos,setTodos]=useState([]);

functionaddTodo() {
todos.push("New task");
setTodos(todos);
}

Why it breaks:

React does not detect changes when you mutate the same array reference.

Fix:

functionaddTodo() {
setTodos(prev => [...prev,"New task"]);
}

Always return a new array or object instead of mutating the existing one.


Mistake 2: Not using functional updates when needed

What you might do:

setCount(count+1);
setCount(count+1);

Why it breaks:

Both calls use the same stale value of count.

Fix:

setCount(prev =>prev+1);
setCount(prev =>prev+1);

Each update now uses the latest value.


Mistake 3: Calling hooks conditionally

What you might do:

if (loggedIn) {
const [count,setCount]=useState(0);
}

Why it breaks:

Hooks must run in the same order on every render.

Fix:

Always declare hooks at the top level of your component.

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

Troubleshooting

  • If state does not update visually, check that you are not mutating objects or arrays.
  • If multiple updates behave strangely, switch to the functional updater.
  • If React throws a hooks error, confirm useState is not inside a condition or loop.
  • If the initial state calculation is expensive, use a lazy initializer:
const [value,setValue]=useState(() =>computeInitialValue());

Quick recap

  • Call useState at the top level of your component.
  • Use direct updates when not depending on previous state.
  • Use the functional updater when you are.
  • Never mutate arrays or objects directly.
  • Hooks must always run in the same order.