Skip to content

useState Hook — Managing State in React

DodaTech 3 min read

In this tutorial, you'll learn about usestate hook. We cover key concepts, practical examples, and best practices.

What You'll Learn

Master React's useState hook — declare and update state, work with objects and arrays, handle forms, follow best practices, and avoid common pitfalls.

Why It Matters

State is the heart of React. Every interactive component needs state. Understanding useState deeply prevents bugs and makes your components predictable.

Real-World Use

A form with multiple inputs, a toggle switch, a counter, a shopping cart with items, or any component that changes based on user interaction.

Basic useState

import { useState } from "react";

function Counter() {
  // Declare state variable: [value, setter]
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

Multiple State Variables

function Form() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [age, setAge] = useState(0);
  const [isSubscribed, setIsSubscribed] = useState(false);

  return (
    <form>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
        placeholder="Name"
      />
      <input
        value={email}
        onChange={e => setEmail(e.target.value)}
        placeholder="Email"
      />
      <label>
        <input
          type="checkbox"
          checked={isSubscribed}
          onChange={e => setIsSubscribed(e.target.checked)}
        />
        Subscribe to newsletter
      </label>
    </form>
  );
}

State with Objects

function UserForm() {
  const [user, setUser] = useState({
    name: "",
    email: "",
    address: {
      street: "",
      city: "",
    },
  });

  // ❌ Wrong — mutates state directly
  function handleWrongInput(e) {
    user.name = e.target.value;  // DON'T DO THIS
  }

  // ✅ Correct — create a new object
  function handleNameChange(e) {
    setUser({
      ...user,  // Spread existing properties
      name: e.target.value,
    });
  }

  // ✅ Nested object update
  function handleStreetChange(e) {
    setUser({
      ...user,
      address: {
        ...user.address,
        street: e.target.value,
      },
    });
  }

  return (
    <div>
      <input value={user.name} onChange={handleNameChange} />
      <input value={user.address.street} onChange={handleStreetChange} />
    </div>
  );
}

State with Arrays

function TodoList() {
  const [todos, setTodos] = useState([]);

  // Add
  function addTodo(text) {
    setTodos([
      ...todos,
      { id: Date.now(), text, completed: false },
    ]);
  }

  // Remove
  function removeTodo(id) {
    setTodos(todos.filter(todo => todo.id !== id));
  }

  // Update (toggle complete)
  function toggleTodo(id) {
    setTodos(todos.map(todo =>
      todo.id === id
        ? { ...todo, completed: !todo.completed }
        : todo
    ));
  }

  // Clear all
  function clearAll() {
    setTodos([]);
  }

  return (
    <div>
      <button onClick={() => addTodo("New task")}>Add</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{
                textDecoration: todo.completed ? "line-through" : "none",
              }}
              onClick={() => toggleTodo(todo.id)}
            >
              {todo.text}
            </span>
            <button onClick={() => removeTodo(todo.id)}>×</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Functional Updates

When new state depends on previous state, use a function:

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

  // ❌ Bug: if this runs multiple times, count might be stale
  function handleClick() {
    setCount(count + 1);  // May use stale value
    setCount(count + 1);  // This doesn't add 2 — uses same count
  }

  // ✅ Correct: functional update
  function handleClick() {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  }
  // Result: count increases by 2
}

Lazy Initial State

If your initial state requires expensive computation:

// ❌ Expensive computation runs on every render
const [data, setData] = useState(expensiveComputation());

// ✅ Runs only once (on initial render)
const [data, setData] = useState(() => expensiveComputation());

Common Mistakes

Mistake Fix
Mutating state directly Always use setter function
Forgetting spread for objects setState({...state, key: value})
Stale closures in effects Use functional updates
Too many useState calls Use useReducer for complex state
Derivable state in useState Compute on the fly instead
Not using callback form setCount(prev => prev + 1)

Practice

// Build a component with:
// 1. A text input
// 2. A counter with increment/decrement/reset
// 3. A list where you can add and remove items
// 4. A toggle switch

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro