Modern React 5: State shape and derived values
This is part 5 of the Modern React development series.
State shape is the difference between a component that naturally stays consistent and a component that spends every render reconciling copies of the same fact. React works best when state stores the smallest set of changing facts and render calculates the rest.
Concept
A derived value is a value that can be calculated from props, state, or constants during render. It usually does not belong in state because React can recalculate it every time the component renders.
Terms
- State shape: The set of state variables and objects a component uses to remember changing data.
- Derived value: A value calculated from existing props or state.
- Source of truth: The one place a fact is stored before other values are calculated from it.
- Normalization: Storing related data by ID or stable keys so updates can target one fact.
Mental model
Think of state as the ingredients, not the plated meal. Store the ingredients that can change. Build the plate during render from those ingredients.
How it is used
Use this model for filtered lists, totals, selection state, form summaries, active tabs, and permission displays. If a value can be computed from current inputs, calculate it during render and let React rerun that calculation when inputs change.
How to use it
- List every value the UI displays or uses for decisions.
- Mark values that change over time and cannot be calculated from existing inputs.
- Store only those changing facts in state.
- Calculate counts, filtered arrays, labels, booleans, and display summaries during render.
- Memoize expensive calculations only after measuring or when the cost is clear.
Example: Filter without duplicated state
import { useState } from "react";import { FilterTabs } from "./FilterTabs";import { TaskList } from "./TaskSummaryList";
type Task = { id: string; title: string; done: boolean };type Filter = "all" | "open" | "done";
export function TaskBoard({ tasks }: { tasks: Task[] }) { const [filter, setFilter] = useState<Filter>("all");
const visibleTasks = tasks.filter((task) => { if (filter === "open") return !task.done; if (filter === "done") return task.done; return true; });
return ( <> <FilterTabs value={filter} onChange={setFilter} /> <p>{visibleTasks.length} visible tasks</p> <TaskList tasks={visibleTasks} /> </> );}2 visible tasks
- Draft release notes
- Verify analytics
visibleTasks and the count are derived. The only local state is the selected filter.
Example: Store IDs instead of objects
import type { ReactElement } from "react";
type User = { id: string; name: string };
export function AssigneeSummary({ users, selectedUserId,}: { users: User[]; selectedUserId: string | null;}): ReactElement { const selectedUser = users.find((user) => user.id === selectedUserId) ?? null;
return <p>{selectedUser ? selectedUser.name : "No assignee"}</p>;}Grace Hopper
An ID is stable across refreshes of the users array. The selected object is derived from the current list.
Details to watch
- Duplicated facts: Two state values that represent the same fact can drift apart.
- Object identity: When data refreshes from a server, object references may change. IDs usually remain stable.
- Expensive derivation: Use
useMemofor expensive pure calculations after the cost matters. - Effects: Do not use an Effect just to copy props or state into another state variable for display.
Series navigation
- Previous: Part 4: Events and local state
- Next: Part 6: Lifting state and controlled inputs
- Series index: Modern React development