webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Tooling
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Forms
  12. 12Data and backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animations
  16. 16Testing
  17. 17Architecture
React · Lesson 2 of 7

State, derivation and reducers

Using useState correctly, immutable updates, derived state and useReducer.

Updated

What state is

State = a component's memory: data that changes over time because of interaction (an input, a toggle, a counter). When the state changes, React re-renders the component with the new value.

Why not a plain variable? A local variable (let count = 0) resets on every render, and changing it doesn't trigger any screen update.

const [count, setCount] = useState(0)
  • countthe current value
  • setCountthe function that changes it and asks for a re-render
  • 0the initial value

Props vs state

Props State
Comes from the parent the component itself
Can be changed inside the component no yes, through the setter
Role configuration from outside internal memory

How the setter works

setCount(count + 1)        // a new value
setCount(c => c + 1)       // an updater — receives the most recent value
  • The setter does not change the variable immediately; it schedules a new render. A console.log(count) right after it shows the old value.
  • React groups several updates (batching) into a single render.
  • When the new value depends on the old one → an updater function: setCount(c => c + 1) 3 times gives +3; setCount(count + 1) 3 times gives +1.

State is immutable

React compares the old state with the new one by reference. You replace objects and arrays, you don't change them — see References and immutability.

setUser({ ...user, name })                       // ✓
setTodos(todos => [...todos, newTodo])           // ✓
user.name = name; setUser(user)                  // ✗ the same reference → no re-render

Derived state — don't store it

If a value can be computed from props or state, compute it while rendering. Don't keep it in another useState.

// ✗ two sources of truth + an effect that syncs them
const [todos, setTodos] = useState([])
const [doneCount, setDoneCount] = useState(0)

// ✓ a single source, the rest derived
const doneCount = todos.filter(t => t.done).length

Truly expensive calculations → useMemo (or the React Compiler, which memoizes automatically).

Where to keep state — kinds and when

Where When Example
local useState only one component uses it an input, an open dropdown
lifted into the parent (lift state up) two siblings need it a filter + a list
the URL (?page=2) it has to be shareable and survive a reload filters, pagination, the active tab
Context many deep components, rare changes the theme, the logged-in user, the language
a store (Zustand) global, changed often a shopping cart in an SPA
a server state cache data from the server Server state

The rule: as low as possible, lift it only when needed.

useReducer — when the logic grows

When a piece of state has many ways to change, you centralize the logic in a reducer — a pure function (state, action) => newState:

function todosReducer(state, action) {
  switch (action.type) {
    case 'add': return [...state, action.todo]
    case 'toggle': return state.map(t => (t.id === action.id ? { ...t, done: !t.done } : t))
    case 'remove': return state.filter(t => t.id !== action.id)
    default: return state
  }
}

const [todos, dispatch] = useReducer(todosReducer, [])
dispatch({ type: 'add', todo })
useState useReducer
1–3 simple values state with many transitions
logic in the handlers logic in a single place, testable on its own

Summary

  • State = the component's memory; the setter asks for a re-render.
  • An updater function when you depend on the old value; always new objects.
  • Derive what you can compute; keep state as low as possible; the URL for what has to be shared.

Official sources

Exercises

Was this page helpful?

One tap — no account needed.