Handbooks / React / Chapter 2

State and Props

7 pages · ~5 min✓ Reviewed

Builds on Components and JSX. Next up: Hooks.

ReactPage 1

Props, State and One-Way Flow

1. Props vs State

PropsState
OwnerParentThe component
Mutable?Read-onlyVia setter only
Change meansNew inputRe-render

2. One-Way Flow

  1. Parent stateuseState
  2. Props downread-only
  3. Childrenders
  4. Callback uponAdd()
jsx
const [n, setN] = useState(0);
<Child n={n} onAdd={() => setN(c => c + 1)} />

data down as n, event up as onAdd

  • UI = f(props, state)
  • A re-render re-runs the function with new values

3. Derive, Don't Store

  • Derivable from props or other state? Compute it in render
  • Stored copies drift out of sync
Never mutate props

The child asks the parent through a callback prop

ReactPage 2

useState and Immutable Updates

1. The Hook and Snapshots

  • const [value, setValue] = useState(initial) → current value + setter
  • initial is used only on the first render
  • Setter call schedules a re-render
  • State is a snapshot: the variable keeps its old value in this render

2. Updater Form

  • New value depends on the previous one → pass c => c + 1
  • Avoids stale values in batched updates
javascript
setCount(count + 1);
setCount(count + 1); // +1 total
setCount(c => c + 1);
setCount(c => c + 1); // +2 total

3. Immutable Updates

  • React compares with Object.is
  • A mutated object keeps its reference → no re-render
GoalPattern
Object{...obj, key: v}
Add[...arr, item]
Removearr.filter(x => x.id !== id)
Editarr.map(x => x.id === id ? {...x, done: true} : x)
Replace, never mutate

Build a new object or array, then pass it to the setter.

ReactPage 3

Lifting State Up

1. Lift to the Common Parent

  • Siblings need the same data: move the state to their closest common parent
  • Parent passes the value as a prop and a setter as a callback prop
  • Child calls the callback; the parent stays the single source of truth
  1. Child eventcalls callback
  2. Parent setStateowns state
  3. Re-rendernew props down

2. Value Down, Callback Up

jsx
function Parent() {
  const [temp, setTemp] = useState('');
  return <>
    <Input value={temp} onChange={setTemp} />
    <Display value={temp} />
  </>;
}

both siblings read one state

3. Mistakes and Limits

  • useState(props.value) in a child: the copy ignores later prop changes
  • Use the prop directly; keep no second copy
SituationReach for
Props pass through many layersContext or composition
Only one component uses itLocal useState
Remember

One owner for the state. Props down, events up.

ReactPage 4

Controlled vs Uncontrolled Inputs

1. Who Holds the Value

  • Controlled: value + onChange bound to state
  • React is the source of truth for the field
jsx
<input value={v}
 onChange={e => setV(e.target.value)} />
  • Uncontrolled: the DOM holds the value
  • Read it with ref.current.value, or on submit via FormData
  • Set the initial value with defaultValue

2. Which to Use

ControlledUncontrolled
Value lives inReact stateThe DOM
SuitsLive validation, formatting, disabling submitSimple forms, file inputs
Initial valueuseState('')defaultValue

3. Common Mistakes

  • value without onChange makes the field read-only and logs a warning
  • undefined to a defined value flips uncontrolled to controlled
Initialise to ''

Start state as '', never undefined, so the input never switches mode.

ReactPage 5

Context Basics

1. Provider and Consumer

  • createContext(default) makes the context
  • useContext(Ctx) gives the nearest Provider value above
  • The default applies only when no Provider is above
jsx
const ThemeCtx = createContext("light");
<ThemeCtx.Provider value={theme}>
  <App />
</ThemeCtx.Provider>
const theme = useContext(ThemeCtx);

no props passed through the layers between

2. State and Re-renders

  • Context is not state: keep useState or useReducer in the Provider
  • Put the value and setter in the context
  • Consumers all re-render when the value changes by reference
  • Memoise object values with useMemo, or split contexts
jsx
const [theme, setTheme] = useState("light");
const value = useMemo(
  () => ({ theme, setTheme }), [theme]);
return <ThemeCtx.Provider value={value}>

stable object until theme changes

Good fitPoor fit
ThemeForm input
Current userMouse position
LocaleRapidly changing data
Mistake

Inline value={{ a, b }} re-renders every consumer.

ReactPage 6

State and Data Flow Cheat Sheet

1. Flow and Setters

  • Props go down, events go up as callback props
  • Props are read-only; state is owned, and a change triggers a re-render
  • State is a snapshot per render: the variable keeps its old value after setX
  • Depends on previous state? Use the updater: setN(n => n + 1)
  • Replace, never mutate: same reference means no re-render

2. Immutable Cheats

DoPattern
Update object{...obj, key: v}
Add[...arr, x]
Removearr.filter(x => x.id !== id)
Editarr.map(x => x.id === id ? {...x, done: true} : x)

3. Where State Lives

  1. One componentlocal useState
  2. Siblingslift to common parent
  3. Many distant consumerscontext

4. Inputs

ControlledUncontrolled
Source of truthReact stateThe DOM
Wiringvalue + onChangeref / defaultValue
Never switch modes

Initialise state to '', not undefined. Going from undefined to a value flips the input from uncontrolled to controlled.

ReactPage 7

Check yourself

Count is 0. One click runs setN(n + 1) three times. What is n after the re-render?
  • 1, not 3
  • State is a snapshot: all three calls set 0 + 1
  • setN(c => c + 1) three times gives 3
todos.push(t); setTodos(todos) never updates the list. Why?
  • Same array reference, so Object.is sees no change
  • Fix: setTodos([...todos, t])
A child does useState(props.value). The parent later passes a new value. What does the child show?
  • The old value
  • The initial value is used only on the first render, so the copy goes stale
  • Use the prop directly, or lift the state