State Architecture Decisions
The most important skill in React state management is knowing WHERE to put state and WHAT kind of state library to use. Getting this wrong at the start of a project creates compounding complexity. This topic gives you the decision framework senior engineers use.
The Three Types of State
- Local UI state — belongs in the component that uses it or its nearest parent. Examples: form input values, dropdown open/closed, loading spinner for a specific button. Use useState or useReducer
- Global client state — shared across many unrelated components and changes over time. Examples: authenticated user, shopping cart, theme/locale. Use Zustand or Redux Toolkit
- Server state — data that lives on the server, needs to be fetched, cached, and synchronized. Examples: product list, user profile, comments. Use TanStack Query or RTK Query — NOT useState+useEffect
- URL state — state encoded in the URL (search params, route params). Use React Router's useSearchParams — it's automatically shareable and bookmarkable
- The #1 mistake is treating server state as client state — putting API responses in Zustand or Context forces you to manage caching, invalidation, and synchronization manually; TanStack Query does this for free
State Co-location Principle
// RULE: State should live as close as possible to where it's used
// Move state UP only when genuinely needed by multiple components
// ❌ Over-lifted state — everything at the top
function App() {
const [isDropdownOpen, setIsDropdownOpen] = useState(false); // used only by Header
const [inputValue, setInputValue] = useState(''); // used only by SearchBar
const [hoveredCard, setHoveredCard] = useState(null); // used only by ProductCard
// App re-renders on every micro-interaction — performance disaster
}
// ✅ Co-located state — each component owns its own UI state
function Header() {
const [isDropdownOpen, setIsDropdownOpen] = useState(false); // lives HERE
}
function SearchBar() {
const [inputValue, setInputValue] = useState(''); // lives HERE
}
function ProductCard({ product }) {
const [isHovered, setIsHovered] = useState(false); // lives HERE
}
// Only lift state when two sibling components need the same data
// And they have a common parent that can hold itDecision Matrix — Which State Tool to Use
- useState: single component, simple values (boolean, string, number, small object). Default choice
- useReducer: single component, complex state with multiple sub-values that update together, or state machine logic
- Context API: low-frequency global state (theme, auth user, locale). Max 2-3 values per context. Split by update frequency
- Zustand: frequently updating shared state, many consumers across the tree, devtools support needed, simpler than Redux
- Redux Toolkit: existing Redux codebase, very complex action flows, large team needing strict conventions, RTK Query for API layer
- TanStack Query: ALL server state — anything fetched from an API. Handles caching, background refresh, optimistic updates, pagination
Tip
Tip
Practice State Architecture Decisions in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
Props flow down, events flow up — unidirectional data flow
Practice Task
Note
Practice Task — (1) Write a working example of State Architecture Decisions from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with State Architecture Decisions is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready react code.
Key Takeaways
- The most important skill in React state management is knowing WHERE to put state and WHAT kind of state library to use.
- Local UI state — belongs in the component that uses it or its nearest parent. Examples: form input values, dropdown open/closed, loading spinner for a specific button. Use useState or useReducer
- Global client state — shared across many unrelated components and changes over time. Examples: authenticated user, shopping cart, theme/locale. Use Zustand or Redux Toolkit
- Server state — data that lives on the server, needs to be fetched, cached, and synchronized. Examples: product list, user profile, comments. Use TanStack Query or RTK Query — NOT useState+useEffect