Learn / Frameworks / React / State with useState

React · Lesson 3 of 7

State with useState

Give components memory and update it without mutating.

  • Beginner
  • 16 min read
  • 3 objectives

Before this lessonLesson 2: JSX and Components

What you will learn

  • Use useState
  • Update objects and arrays immutably
  • Lift state up

A regular variable inside a component is reset every time the component runs and, crucially, changing it does not tell React to redraw. State solves both problems: it is memory that React preserves between renders, and updating it triggers a new render.

useState

import { useState } from "react";

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

useState(0) returns the current value and a setter. The argument is only the initial value. Calling setCount schedules a re-render with the new value. Hooks must be called at the top level of a component, never inside loops, conditions or nested functions.

State updates are snapshots

Within one render, count is a fixed number. Calling setCount(count + 1) three times adds only one, because each call sees the same count. When the next value depends on the previous one, pass an updater function.

function addThree() {
  setCount((c) => c + 1);
  setCount((c) => c + 1);
  setCount((c) => c + 1);   // total +3
}

Never mutate state

Treat state as immutable. Create a new object or array instead of editing the existing one; otherwise React cannot detect the change.

const [user, setUser] = useState({ name: "Ada", age: 36 });
const [todos, setTodos] = useState(["write", "test"]);

// objects: copy then override
setUser({ ...user, age: 37 });

// arrays
setTodos([...todos, "deploy"]);                    // add
setTodos(todos.filter((t) => t !== "test"));       // remove
setTodos(todos.map((t) => (t === "write" ? "edit" : t)));   // change

Lifting state up

When two components need the same data, move the state to their closest common parent and pass it down as props, along with functions to change it. This keeps one source of truth.

function Parent() {
  const [query, setQuery] = useState("");
  return (
    <>
      <SearchBox value={query} onChange={setQuery} />
      <Results query={query} />
    </>
  );
}

Deriving values

Do not store something in state if you can compute it from existing state or props. Storing fullName alongside first and last creates a chance for them to disagree; compute it during render instead.

// Write your solution here
Up next · Lesson 4Events, Lists and FormsHandle events, render lists with keys and build controlled forms.