Store
Keep information that changes and cannot be calculated from current props or state.
Good state design gives every changing value one clear owner. Store the smallest source of truth, calculate what follows from it, and pass data down to the components that render it.
Before adding useState, list the pieces of data the interface needs. Then separate values the app must remember from values it can calculate. This prevents two state variables from disagreeing.
Keep information that changes and cannot be calculated from current props or state.
Calculate values that follow from the source of truth during rendering.
Move state to the closest common parent when multiple children need to coordinate.
State is the information React must remember between renders. More state is not more capable. Every extra state variable adds another value that code must initialize, update, and keep consistent.
If so, declare it outside the component or as a regular constant, not state.
Read the prop directly. Copying a prop into state creates a second source of truth unless the copy intentionally represents a separate value, such as an editable draft.
If so, derive it during render instead of synchronizing another state variable.
If it changes over time and the earlier questions did not remove it, it is a good state candidate.
import { useState } from "react";
function ProductSearch({ products }) {
const [query, setQuery] = useState("");
// Derived during this render, so it always matches query and products.
const visibleProducts = products.filter(product =>
product.name.toLowerCase().includes(query.toLowerCase())
);
const resultCount = visibleProducts.length;
return (
<section>
<input
value={query}
onChange={event => setQuery(event.target.value)}
placeholder="Search products"
/>
<p>{resultCount} results</p>
<ul>
{visibleProducts.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</section>
);
}
The source of truth is small: the parent owns products, and this component owns query. It does not need visibleProducts or resultCount in state.
A derived value is a calculation based entirely on current props or state. Regular JavaScript calculations inside the component run whenever it renders, so the result automatically describes the same snapshot as the JSX.
Storing fullName requires extra updates and can briefly leave the interface showing an old value.
Calculating fullName makes it correct whenever either source value changes.
function NameEditor() {
const [firstName, setFirstName] = useState("Asha");
const [lastName, setLastName] = useState("Rao");
// This is current on every render. No fullName state is needed.
const fullName = `${firstName} ${lastName}`;
return (
<form>
<input
value={firstName}
onChange={event => setFirstName(event.target.value)}
/>
<input
value={lastName}
onChange={event => setLastName(event.target.value)}
/>
<p>Welcome, {fullName}</p>
</form>
);
}
Deriving a value does not mean calculating it only once. It means calculating it from the current render's inputs. If a calculation is measurably expensive, useMemo can cache it as a performance optimization, but it should not be needed for correctness.
Two sibling components cannot directly share local state. Move their shared state to their closest common parent. The parent passes each child the current value and an event handler that lets the child request a change.
function Accordion() {
const [activeId, setActiveId] = useState(1);
return (
<>
<Panel
title="About"
isOpen={activeId === 1}
onOpen={() => setActiveId(1)}
>
React builds interfaces from components.
</Panel>
<Panel
title="Details"
isOpen={activeId === 2}
onOpen={() => setActiveId(2)}
>
The parent coordinates both panels.
</Panel>
</>
);
}
function Panel({ title, isOpen, onOpen, children }) {
return (
<section>
<h3>{title}</h3>
{isOpen ? children : <button onClick={onOpen}>Open</button>}
</section>
);
}
Lift state only as high as coordination requires. Keeping unrelated state near the component that uses it makes ownership easier to understand and avoids rerendering a larger subtree without a reason.
React data flows from parent to child through props. A child does not modify those props. It reports an interaction by calling a handler prop, the owner updates state, and the new value flows down in the next render.
isOpen to a child.onOpen after a click.“Events go up” is a useful shorthand, but React does not automatically send a custom event to the parent. The parent explicitly passes a function, and the child explicitly calls it.
A component is controlled when important information about its behavior comes from props. Its parent owns that state. The child renders the value and uses callback props to request changes.
function SearchPage() {
const [query, setQuery] = useState("");
return (
<SearchInput
value={query}
onChange={setQuery}
/>
);
}
function SearchInput({ value, onChange }) {
return (
<input
value={value}
onChange={event => onChange(event.target.value)}
/>
);
}
For a custom component, a prop such as isOpen controls behavior. For a form input, value or checked makes React state the source of truth.
A custom component may initialize and manage its own state. A DOM input using defaultValue keeps its current value in the DOM.
Controlled components are easier for a parent to synchronize, validate, or reset. Uncontrolled components can need less wiring when no other component cares about their state.
Do not pass value without a matching onChange that updates it unless the field is intentionally read-only. Otherwise, the input appears frozen because every render restores the same value.
Ignore values that never change and values already supplied as props.
Keep one source of truth and calculate labels, filtered collections, totals, and booleans from it.
Keep state local unless multiple components need the same value, then lift it to their closest common parent.
Pass current values down as props and pass handlers down so children can request updates.
items, filter, and visibleItems. Which state is probably redundant?
visibleItems is derived from items and filter, so calculate it during render. Storing it creates another value that can fall out of sync.
Put one shared value, such as activePanelId, in their closest common parent. Pass each panel its open status and an event handler.
React supplies its current value or checked prop, and an event handler updates the state that supplies that prop.