Use State

Course 1 · Ch 3
State with useState
Giving a component data it can change itself, and re-rendering automatically when it does

Props let a parent hand data down to a child, but they're read-only from the child's side. State is the other half of the picture — data a component owns and can change itself, in response to things like a click or a timer. Whenever a piece of state changes, React automatically re-renders that component (and re-runs the virtual DOM comparison from Chapter 1) to reflect the new value.

The useState Hook

import { useState } from "react"; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); }

useState(0) creates one piece of state, starting at 0, and returns an array with exactly two things: the current value (count) and a setter function (setCount) used to change it. Calling setCount(count + 1) tells React "this component's state changed, please re-render it" — the component function runs again, this time with count holding the new value.

useState is a "hook" — and hooks have rules
Functions starting with use are called hooks — special functions that "hook into" React features like state. They must be called directly inside a component function (or another hook), always at the top level — never inside an if, a loop, or a nested function. This guide will introduce several more hooks across the next few chapters; the same rule applies to all of them.

Why Not Just a Regular Variable?

// this does NOT work as expected function BrokenCounter() { let count = 0; return <button onClick={() => count++}>{count}</button>; }

A plain let count = 0 would actually increment in memory each click, but React has no reason to re-render the component — nothing told it the UI needs updating, so the displayed number never changes on screen. setCount exists specifically to trigger that re-render; a regular variable reassignment is invisible to React.

Updating Based on the Previous Value

setCount(prevCount => prevCount + 1);

Passing a function to the setter (instead of a value directly) receives the most up-to-date previous value as its argument. This matters when several updates might happen close together — React guarantees each functional update sees the result of the one before it, whereas calling setCount(count + 1) twice in a row can both read the same stale count and only increment once instead of twice.

Multiple Independent State Variables

function ProfileForm() { const [name, setName] = useState(""); const [isEditing, setIsEditing] = useState(false); return ( <div> <p>Name: {name || "(not set)"}</p> <button onClick={() => setIsEditing(true)}>Edit</button> </div> ); }

A single component can call useState as many times as it needs — each call manages a completely independent piece of state, with its own value and its own setter. There's no requirement to bundle everything into one big state object the way some older patterns did; usually several small, clearly-named pieces of state read better than one combined one.

Never mutate state directly
Calling something like user.name = "New Name" directly on a state object (instead of calling its setter with a new object) won't trigger a re-render, and risks subtle bugs even when it happens to work visually. Always treat state as read-only outside of its setter — for objects and arrays, that means creating a new copy with the change applied, rather than editing the existing one in place. This becomes especially important in Intermediate Chapter 3 (useReducer) and is worth getting into the habit of now.
PatternWhen to use it
setCount(5)Setting state to a known, fixed value
setCount(count + 1)Simple update based on the current render's value
setCount(prev => prev + 1)Safer update when multiple changes might happen in quick succession

Coding Challenges

Challenge 1

Build a Counter component with +1 and -1 buttons, both updating the same count state, and a "Reset" button that sets count back to 0.

📄 View solution
Challenge 2

Build a LightSwitch component with a boolean isOn state (starting false) and a single button that toggles it, displaying "On" or "Off" based on the current value.

📄 View solution
Challenge 3

Build a component with two separate pieces of state: a numeric clickCount, and a string lastClicked storing the current time (use new Date().toLocaleTimeString()). One button updates both at once when clicked.

📄 View solution

Chapter 3 Quick Reference

  • useState(initialValue) — returns [value, setValue] for one piece of state
  • Calling the setter triggers a re-render — a plain variable reassignment does not
  • Functional updatessetCount(prev => prev + 1) — safer when updates happen close together
  • A component can call useState multiple times for independent pieces of state
  • Never mutate state directly — always go through its setter
  • Hooks (functions starting with use) must be called at the top level of a component, never conditionally
  • Next chapter: event handling — the onClick pattern used here, covered properly across more event types