Lesson 31 of 45 beginner

State and Lifecycle

State is memory that triggers a new render, and every render is a snapshot

Guess first

Pick an answer. You will see if you were right at the end of the lesson.

You call setCount(count + 1) three times in one click handler, starting at 0. What is count after the next render?

Open interactive version (quiz + challenge)

Real-world analogy

A meeting room shows a printed photo of a whiteboard. To change it, you hand the assistant a new board, and the assistant prints a fresh photo for the room. If you scribble on the printed photo yourself, the assistant never knows, so no new photo is printed. The board is state, each printed photo is one render, and handing over a new board is calling the setter.

What is it?

State is data that a component remembers between renders. When you change it through its setter, React renders the component again. Each render is a snapshot: the function runs with one fixed set of values and produces the UI for them. To change what is on screen, you do not edit the page. You set new state, and React runs the function again and applies the difference.

Real-world relevance

A todo list holds an array in state. Adding a task builds a new array with the new item at the end. Toggling one builds a new array where one object is copied with a flipped flag. The "items left" counter is computed from the array on each render, not stored. Teams that mutate the array in place hit the classic bug where the data changed but the screen did not.

Key points

Quick check

Answer from memory — scrolling up first makes it stick less.

Which line updates a todo list correctly?

Two sibling components need the same value. Where should the state live?

Code example

import { useState } from 'react';

type Todo = { id: string; text: string; done: boolean };
export function TodoApp() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [text, setText] = useState('');
  const add = () => {
    if (!text.trim()) return;
    setTodos(prev => [...prev, { id: crypto.randomUUID(), text, done: false }]);
    setText('');
  };
  const toggle = (id: string) =>
    setTodos(prev => prev.map(t => (t.id === id ? { ...t, done: !t.done } : t)));

  const remaining = todos.filter(t => !t.done).length;
  return (
    <div>
      <input aria-label="New todo" value={text} onChange={e => setText(e.target.value)} />
      <button type="button" onClick={add}>Add</button>
      <p>{remaining} left</p>
      <ul>
        {todos.map(t => (
          <li key={t.id}>
            <label>
              <input type="checkbox" checked={t.done} onChange={() => toggle(t.id)} />
              {t.text}
            </label>
          </li>
        ))}
      </ul>
    </div>
  );
}

Line-by-line walkthrough

  1. 1. Two pieces of state, both typed: todos is an array of Todo objects, and text is the string in the input.
  2. 2. add builds a new array with spread and appends the new item. The functional form uses the latest list.
  3. 3. toggle uses map to return a new array. The matching todo is copied with a flipped done flag, and the others are reused as they are.
  4. 4. remaining is derived from todos on each render. It is not stored, so it cannot drift from the list.
  5. 5. The input is controlled: its value comes from state, and onChange writes back to state.
  6. 6. Each list item has key={t.id}, and the checkbox calls toggle with that id.

Spot the bug

// assume: import { useState } from 'react';
function Cart() {
  const [items, setItems] = useState<string[]>([]);
  function addItem(name: string) {
    items.push(name);
    setItems(items);
  }
  return <button onClick={() => addItem('tea')}>Add ({items.length})</button>;
}
Need a hint?
Is the array that setItems receives a different object from the old one?
Show answer
push changes the existing array and setItems gets the same reference, so React sees no change and skips the re-render. Fix: create a new array, setItems(prev => [...prev, name]).

Explain like I'm 5

State is a notebook the page keeps. When you write a new page in the notebook, the screen redraws. If you only scribble on the old page and say nothing, the screen does not notice.

Fun fact

In development, React Strict Mode renders each component twice on purpose. It does this to expose code that depends on a render happening only once.

Hands-on challenge

Say it out loud in 30 seconds: "Why must you not mutate state in React?" Then check: did you mention comparing references, a new array or object, the functional update form, and that a render is a snapshot?

More resources

Open interactive version (quiz + challenge) Download the whole course as PDF / Word

Lock it in

← Back to course: .NET + React Playbook