Course navigationPhase 23
Volume 06 · Phase 23

Professional Frontend State Architecture

Choose the correct owner for URL state, server state, local UI state, forms, and global client state instead of duplicating everything.

16lessons + labs
4klesson words
9source references
What you will ship

Phase outcome and project increment

Learning outcome

Choose the correct owner for URL state, server state, local UI state, forms, and global client state instead of duplicating everything.

Concrete deliverable

A state ownership map for TaskFlow plus refactored examples removing redundant synchronized state.

Figure 23.1 — Where this phase lives in TaskFlow
classify state
choose authoritative owner
derive values
share only when necessary
avoid synchronization loops
Trace the flow left-to-right for implementation and right-to-left for debugging. At every arrow, ask what data crosses the boundary and what proves it is trustworthy.
Instructor mental model

Principles to keep while coding

01

Put shareable navigation state in the URL, server state in TanStack Query, and local interaction state near the component that owns it.

02

Avoid duplicating the same piece of state across URL, React state, and query cache.

03

Derive values when possible instead of synchronizing multiple mutable copies.

04

Choose state ownership by lifecycle and source of truth, not by whichever library is fashionable.

Code-along path

Guided phase lab

Do these in order. Each step should end with evidence before the next one starts.

  1. 01

    Inventory every state variable on one TaskFlow screen and classify it: server, URL, form, local UI, or cross-cutting client.

  2. 02

    Move filter/search/page state that should survive/share navigation into URL search params.

  3. 03

    Remove local copies of TanStack Query data and derive filtered/presentational values during render.

  4. 04

    Keep ephemeral dialog/menu state at the smallest component that owns it.

  5. 05

    Evaluate whether any global store is actually needed; write the requirement before adding one.

  6. 06

    Create a diagram showing source of truth and allowed data flow for the screen.

Debugging studio

Failures you should know how to diagnose

Failure drill 1

useEffect syncing query data into local state

Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.

Failure drill 2

Global store used for all state categories

Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.

Failure drill 3

URL and React state disagreeing about filters

Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.

Failure drill 4

Storing derived values that can be computed

Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.

Watch-order style

Lessons

The source chapters come first. Every phase now ends with a guided build, a debugging workshop, and an independent challenge so reading turns into transferable skill.

01

State Taxonomy and Ownership Decision Framework

State Taxonomy and Ownership Decision Framework Volume 06 - Professional Frontend | Phase 23 The first question is ownership, not API choice When developers ask "Should this use Context or Z

2 min
02

Avoid Redundant Duplicate and Synchronized State

Avoid Redundant, Duplicate and Synchronized State Volume 06 - Professional Frontend | Phase 23 The most expensive state is copied state Every duplicate introduces a synchronization problem.

2 min
03

Local UI State useState and Component Ownership

Local UI State, useState and Component Ownership Volume 06 - Professional Frontend | Phase 23 Keep ephemeral interaction state local Mobile navigation visibility, a disclosure panel, a tempo

2 min
04

URL State Filters Pagination and Deep Linkability

URL State: Filters, Pagination and Deep Linkability Volume 06 - Professional Frontend | Phase 23 The URL is a state container Phase 15 already made Task filters, sorting, and pagination part

2 min
05

Form State vs Server State

Form State vs Server State Volume 06 - Professional Frontend | Phase 23 A form is an intentional divergence from the server This is one of the few places where a local copy is not accidental

2 min
06

Auth Session Workspace and Cross Tree Context

Auth, Workspace and Cross-Tree Context Volume 06 - Professional Frontend | Phase 23 Context is a delivery mechanism, not automatically the owner Context is useful when many descendants need

2 min
07

useReducer for Complex Local Transitions

useReducer for Complex Local Transitions Volume 06 - Professional Frontend | Phase 23 Reducer is not a global store useReducer is valuable when one local feature has several related transiti

2 min
08

Derived State Selectors and Computation

Derived State, Selectors and Computation Volume 06 - Professional Frontend | Phase 23 If it can be computed, prefer computing it Derived state is information that can be calculated from auth

2 min
09

Server Client Boundaries and State Placement

Server/Client Boundaries and State Placement Volume 06 - Professional Frontend | Phase 23 Client state creates a client boundary In the App Router, Server Components are still the default ar

2 min
10

When Context or Global State is Actually Justified

When Context or Global State Is Actually Justified Volume 06 - Professional Frontend | Phase 23 Do not ban global state; earn it After moving server state to TanStack Query, forms to React H

2 min
11

State Testing Debugging and Common Failures

State Testing, Debugging and Common Failures Volume 06 - Professional Frontend | Phase 23 Debug ownership before debugging values When a screen shows stale or contradictory information, ask

2 min
12

Hands On Lab and Phase Exit

Hands-On Lab and Phase Exit Volume 06 - Professional Frontend | Phase 23 Lab 1 - State inventory Create an inventory of every stateful value on the Tasks screen. For each value, record categ

2 min
LAB

Guided Build Lab

Purpose Choose the correct owner for URL state, server state, local UI state, forms, and global client state instead of duplicating everything.

3 min
LAB

Debugging Workshop

Goal Learn to diagnose this phase's failures systematically instead of reinstalling packages, changing random configuration, or copying a fix without understanding it.

2 min
LAB

Phase Synthesis & Independent Challenge

Mission Rebuild the capability of Phase 23 without following the exact code line-by-line. The goal is transfer: can you use the ideas when the variable names, UI, or route shape changes?

2 min
LAB

Mastery Capstone Lab

Integrate Phase 23 without following a transcript.

2 min
Practice ladder

Exercises that increase independence

Guided

Inventory every state variable on one TaskFlow screen and classify it: server, URL, form, local UI, or cross-cutting client.

Practice

Remove local copies of TanStack Query data and derive filtered/presentational values during render.

Debug

reproduce and diagnose — useEffect syncing query data into local state.

Independent

deliver A state ownership map for TaskFlow plus refactored examples removing redundant synchronized state.

Explain-back

Can classify state before choosing library and Can remove duplicate state.

Mastery gate

Do not leave the phase only because you reached the bottom

Phase exit criteria

Knowledge check

Scenario and concept quiz

These questions test engineering choices rather than whether you remember a lesson title.

0/0 correct · 0/8 answered
01Which practice best matches Phase 23's engineering goal?
02Which practice best matches Phase 23's engineering goal?
03Which practice best matches Phase 23's engineering goal?
04Which practice best matches Phase 23's engineering goal?
05You are working through the Phase 23 lab. Which next action is the most disciplined step?
06You are working through the Phase 23 lab. Which next action is the most disciplined step?
07You are working through the Phase 23 lab. Which next action is the most disciplined step?
08You are working through the Phase 23 lab. Which next action is the most disciplined step?
Source snapshot

Reference files

Attempt the work first. Use snapshots to compare a checkpoint, not as a substitute for understanding.

reference/TaskFlow_State_Architecture_Snapshot/README.md
# TaskFlow Phase 23 Reference Snapshot

This snapshot demonstrates state ownership decisions. It is intentionally small and does not add a global state library.

Owners:
- TanStack Query: server state
- Next.js URL: filters/pagination/shareable navigation
- React Hook Form: unsaved form drafts
- Supabase Auth: authentication session
- React local state/reducer: ephemeral UI interaction
- Focused Context: only stable cross-tree convenience values
reference/TaskFlow_State_Architecture_Snapshot/SOURCES.md
# Sources

- React - Sharing State Between Components: https://react.dev/learn/sharing-state-between-components - Single source of truth; lift shared state to the closest common owner.
- React - Managing State: https://react.dev/learn/managing-state - Avoid redundant/duplicate state; structure state intentionally.
- Next.js - useSearchParams: https://nextjs.org/docs/app/api-reference/functions/use-search-params - URL query state in the App Router.
- TanStack Query - Query State / Cache: https://tanstack.com/query/latest/docs/framework/react/reference/interfaces/QueryState - Server-state cache is separate from local UI state.
- TanStack Query - SSR/Hydration: https://tanstack.com/query/latest/docs/framework/react/guides/ssr - Request-scoped QueryClient and cache isolation.
reference/TaskFlow_State_Architecture_Snapshot/STATE_OWNERSHIP_MATRIX.md
# TaskFlow State Ownership Matrix

| Value | Owner | Why | Reset / lifetime |
|---|---|---|---|
| Task collection | TanStack Query | Remote server state | Cache freshness/auth lifecycle |
| status/search/page | URL | Shareable navigation | Route change |
| Task edit draft | React Hook Form | Intentional unsaved divergence | reset/cancel/success |
| Supabase session | Supabase Auth | Identity provider owns session | logout/expiry |
| mobile menu open | local useState | Ephemeral shell interaction | close/unmount |
| selected Task rows | local reducer | Feature interaction state | clear/filter/page change |
| current workspace slug | URL route scope | Reconstructable tenant scope | navigation |
| resolved workspace metadata | focused Context | convenient deep distribution | route/workspace change |
| completed count | derived | Calculated from authoritative result | render |
reference/TaskFlow_State_Architecture_Snapshot/apps/web/src/components/tasks/anti-pattern-copy-query-data.tsx
// Anti-pattern kept only as documentation. Do not use in TaskFlow.

const tasksQuery = useQuery(taskListOptions(query))
const [tasks, setTasks] = useState<Task[]>([])

useEffect(() => {
  if (tasksQuery.data) setTasks(tasksQuery.data.data)
}, [tasksQuery.data])

// Prefer:
const tasks = tasksQuery.data?.data ?? []
reference/TaskFlow_State_Architecture_Snapshot/apps/web/src/components/tasks/task-selection.reducer.ts
export type SelectionState = {
  ids: Set<string>
}

export type SelectionAction =
  | { type: "toggle"; id: string }
  | { type: "replace"; ids: string[] }
  | { type: "clear" }

export function selectionReducer(
  state: SelectionState,
  action: SelectionAction,
): SelectionState {
  switch (action.type) {
    case "toggle": {
      const ids = new Set(state.ids)
      ids.has(action.id) ? ids.delete(action.id) : ids.add(action.id)
      return { ids }
    }
    case "replace":
reference/TaskFlow_State_Architecture_Snapshot/apps/web/src/components/tasks/tasks-client-region.tsx
"use client"

import { useReducer } from "react"
import { useQuery } from "@tanstack/react-query"

export function TasksClientRegion({ query }: { query: TaskListQuery }) {
  const tasksQuery = useQuery(taskListOptions(query))
  const [selection, dispatch] = useReducer(selectionReducer, { ids: new Set<string>() })

  // Server rows are rendered directly from the query cache.
  const tasks = tasksQuery.data?.data ?? []

  const completedCount = tasks.filter(task => task.status === "DONE").length

  return (
    <TasksView
      tasks={tasks}
      completedCount={completedCount}
      selectedIds={selection.ids}
      onToggle={(id) => dispatch({ type: "toggle", id })}
reference/TaskFlow_State_Architecture_Snapshot/apps/web/src/components/workspace/workspace-context.tsx
"use client"

import { createContext, useContext } from "react"

export type WorkspaceSummary = {
  id: string
  slug: string
  name: string
}

const WorkspaceContext = createContext<WorkspaceSummary | null>(null)

export function WorkspaceProvider({
  workspace,
  children,
}: {
  workspace: WorkspaceSummary
  children: React.ReactNode
}) {
  return (
reference/TaskFlow_State_Architecture_Snapshot/apps/web/src/lib/state/task-list-query.ts
export type TaskListQuery = {
  page: number
  pageSize: number
  search?: string
  status?: string
  priority?: string
}

export function normalizeTaskListQuery(
  searchParams: URLSearchParams,
): TaskListQuery {
  return {
    page: Math.max(1, Number(searchParams.get("page") ?? 1)),
    pageSize: Math.min(100, Math.max(1, Number(searchParams.get("pageSize") ?? 20))),
    search: searchParams.get("search")?.trim() || undefined,
    status: searchParams.get("status") || undefined,
    priority: searchParams.get("priority") || undefined,
  }
}
reference/TaskFlow_State_Architecture_Snapshot/tests/state-architecture-test-matrix.md
# Phase 23 State Architecture Test Matrix

1. Change status filter -> URL changes, page resets to 1.
2. Refresh -> same filter/page reconstructs.
3. Back/Forward -> collection follows browser history.
4. Background query refetch -> Task table updates without a copied local array.
5. Dirty edit form -> query refetch does not overwrite unsaved draft.
6. Successful save -> form reset + relevant query cache updated/invalidated.
7. Mobile menu -> local interaction only; no global store.
8. Logout -> Query cache cleared; previous user server data not shown.
9. Workspace route change -> resolved workspace Context updates from route/server owner.
10. Derived completed count -> changes automatically with task data, no effect synchronization.