Skip to content

React useState Hook Explained with Examples

Learn how the React useState hook works with real-world examples, syntax breakdowns, and best practices. Ideal for new and growing frontend developers in 2025.

3 min readReact Js

Introduction: What is useState?

React is a powerful JavaScript library for building interactive user interfaces — and at the core of its interactivity lies something called state. State is what allows your app to change over time, respond to user actions, and update the UI dynamically.

The useState() hook is the simplest and most commonly used React hook. It enables function components to store and update state — previously only possible in class components. If you're using modern React (and you are), understanding useState is non-negotiable.

  • What useState() does
  • How to use it
  • When to use it
  • Common patterns and mistakes
  • Best practices for 2025

What is useState()?

useState() is a function from the React library that allows you to add local state to functional components.

Before hooks were introduced in React 16.8, managing state was only possible in class-based components. Now, with useState(), you can write cleaner, smaller, and more maintainable functional components — with built-in reactivity.

Basic Syntax

const [state, setState] = useState(initialValue);
  • state : the current state value
  • setState : a function used to update the state
  • initialValue : the default value for the state when the component mounts

Example 1: Basic Counter

Let’s look at the most common example — a button that increments a number on click.

import { useState } from 'react';

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

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

Each time the button is clicked, setCount updates the state, and the component re-renders with the new value.

State Can Be Any Type

The beauty of useState is that it works with any data type — strings, booleans, arrays, objects, or even null.

Examples

const [name, setName] = useState("React");
const [isOpen, setIsOpen] = useState(false);
const [todos, setTodos] = useState([]);
const [user, setUser] = useState({ name: "", email: "" });

You can use useState to control everything from dropdown visibility to form data and list items.

Updating State Correctly

To update state, always use the updater function (setState) — never mutate the state directly.

Example

setCount(count + 1);

// For updates that depend on previous state:
setCount(prev => prev + 1);

This avoids bugs related to stale closures, especially when calling setState multiple times in a single event.

Avoid Direct Mutation

One of the most common mistakes developers make is mutating the state instead of updating it immutably.

Bad:

todos.push("New Task");
setTodos(todos);

Good:

setTodos([...todos, "New Task"]);

React depends on immutability to detect changes — so always return a new object or array when updating state.

Best Practices for useState (2025 Edition)

Use one state per logical concern

const [name, setName] = useState("");
const [email, setEmail] = useState("");

Initialize with the correct type

// TypeScript example:
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);

Use updater functions when working with previous state

setCount(prev => prev + 1);

Avoid nesting state updates

If your component’s logic is getting complicated, consider switching to useReducer() — it's more predictable for complex workflows.

When to Use useState

The useState hook is perfect for:

  • Form inputs (text, checkbox, radio)
  • Modal visibility (open/close state)
  • Tabs and toggles
  • Count and rating systems
  • Theme or language toggles (for small apps)
  • Shared state across multiple components
  • Complex data logic with multiple dependencies
  • Global state like auth, roles, or large forms
  • Context API for simple global state
  • useReducer() for complex logic
  • State management libraries like Zustand or Redux for large-scale apps

Real-World Example: Managing Form State

function LoginForm() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <input value={password} onChange={e => setPassword(e.target.value)} />
      <button type="submit">Login</button>
    </form>
  );
}

Clean, reactive, and easy to extend.

Common useState Pitfalls (and Fixes)

Direct mutation of arrays/objects

Use spread syntax or functional updates

Ignoring previous state dependency

Use updater function: setCount(prev => …)

Using one large object for all state

Split into multiple useState() calls

Trying to share state with siblings

Use Context or lift state to parent

Summary

The useState() hook is the foundation of interactivity in React’s modern, functional approach. It allows your components to respond to users, change dynamically, and keep the UI in sync with your data.

What you’ve learned:

  • The syntax and signature of useState
  • How to initialize and update state correctly
  • Examples for counters, form fields, booleans, and lists
  • How to avoid common mistakes
  • When to use useState , and when not to
Working on something

Need this built properly?

I build the web and mobile products these notes come out of. A first conversation costs nothing and often ends with a smaller scope than you expected.

Book a free consultation