C
React/State/Lesson 07

useState

30 min·theory
This chapter
1/2
JavaScript

useState

💡 Why should you learn this?

🎯 This is the most fundamental way to store and update values displayed on the screen in React.
💼 It is essential for responding to user interactions such as button clicks and text input.
This is the single most important feature that every React developer uses every day.
🏢 실무에서는
In production, useState is used when pressing a 'Like' button increments a count, or when a toggle button opens and closes a menu. The quantity increment/decrement buttons in an online shop are all built with useState.

useState — A component's memory: when the value changes, the screen changes too

Real-world analogy: A Whiteboard

A component has its own whiteboard.

  • state: the value written on the whiteboard
  • setState: the act of erasing and writing a new value
  • When the whiteboard changes → the screen is automatically redrawn

Why learn this?

Changing a regular variable does not update the screen.

You must manage it with useState so React can detect the change and re-render.

Basic usage

js
const [state, setState] = useState(initialValue);
// state: current value
// setState: function to change the value

Things to watch out for

  • Never mutate state directly without setState → the screen will not update
  • setState is asynchronous → the state value may not be updated immediately after the call
  • Arrays and objects must be replaced with a new reference (immutability)
💻 Counter + Toggle + Input form examples
import { useState } from 'react';

// ===== Counter Component =====
function Counter() {
  // Input: initial value 0
  const [count, setCount] = useState(0);

  // Process: increment/decrement/reset
  return (
    <div>
      <p>Current count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

// ===== Toggle Component =====
function Toggle() {
  const [isOn, setIsOn] = useState(false);

  return (
    <button onClick={() => setIsOn(!isOn)}>
      {isOn ? "🟢 ON" : "⚫ OFF"}
    </button>
  );
}

// ===== Input Form Component =====
function NameForm() {
  const [name, setName] = useState("");

  // Output: display real-time input value
  return (
    <div>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
      <p>{name ? `Hello, ${name}!` : "Please enter your name"}</p>
    </div>
  );
}
💻 2025 recommended patterns — functional updates and guaranteed immutability
// ✅ Good example - functional updates, lazy initialization, immutability
import { useState, useCallback } from 'react';
import { produce } from 'immer'; // 2025 standard immutability library

interface User {
  id: number;
  name: string;
  settings: { theme: string; notifications: boolean; };
}

function GoodExample() {
  // Lazy initialization - passed as a function, executed only on first render
  const [count, setCount] = useState(() => {
    console.log('Initialization executed only once');
    return expensiveCalculation();
  });
  
  const [user, setUser] = useState<User>(() => ({
    id: 1,
    name: 'John',
    settings: { theme: 'light', notifications: true }
  }));

  // Functional updates solve closure issues
  const handleMultipleClicks = useCallback(() => {
    setTimeout(() => {
      setCount(prevCount => prevCount + 1); // Always based on the latest state
      setCount(prevCount => prevCount + 1); // Exactly +2
    }, 1000);
  }, []);

  // Immutable updates using immer (recommended for 2025)
  const toggleNotifications = useCallback(() => {
    setUser(prevUser => 
      produce(prevUser, draft => {
        draft.settings.notifications = !draft.settings.notifications;
      })
    );
  }, []);

  // Or spread operator method
  const toggleTheme = useCallback(() => {
    setUser(prevUser => ({
      ...prevUser,
      settings: {
        ...prevUser.settings,
        theme: prevUser.settings.theme === 'light' ? 'dark' : 'light'
      }
    }));
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Theme: {user.settings.theme}</p>
      <p>Notifications: {user.settings.notifications ? 'On' : 'Off'}</p>
      <button onClick={handleMultipleClicks}>Add 2</button>
      <button onClick={toggleNotifications}>Toggle Notifications</button>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
}

function expensiveCalculation(): number {
  return Array.from({length: 1000000}, (_, i) => i).reduce((a, b) => a + b, 0);
}

💡 ⚠️ Common mistakes

  • Passing the result of a function call directly as the initial value of useState, causing unnecessary computation on every render
  • Referencing current state directly instead of using a functional update when the update depends on the previous state, leading to closure bugs
  • Mutating object or array state in place so that React cannot detect the change

💡 🎯 Interview prep

Q: When is a functional update in useState necessary, and why?
Q: Why is state immutability important in React, and how do you implement it?

Hint: Functional updates are needed to resolve closure issues and to safely update based on the previous state. Immutability is important for React's shallow-comparison optimization and predictable state management. Be sure to mention concrete implementation approaches such as immer or the spread operator.

⚛️ React pattern — useState

Learn step by step, with code, how useState is used in React.
1 📦 1. Declare State
Create component-local state with useState
const [count, setCount] = useState(0);
2 👁️ 2. Rendering
Display the state value in JSX
return <h1>Current count: {count}</h1>;
3 🔄 3. Update
Call setState → trigger re-render
<button onClick={() => setCount(count + 1)}>+1</button>
4 💡 4. Core principle
state is immutable — never mutate directly; always use the setter

✨ useState — Build a counter

Edit the code directly and it will be reflected automatically after 0.7 seconds. Runs instantly in the browser with React 18 + Babel.
🖥️ Result — rendered React component
✏️ React 코드 수정하기 (클릭해서 열기)
⚛️ React 18 + Babel Standalone — see the result first, then edit the code yourself.

Check your understanding

What happens if you change state directly without using setState?
💡 When you mutate state directly, the value in memory changes, but React cannot detect the change and the screen will not re-render. A re-render is only triggered when you update state through the setState function!
Read this first: Lists and key
Up next: Form Handling
useState - React