跪拜 Guibai
← All articles
Architecture · React.js · Frontend Framework

A React Todo App as a Blueprint for Architecture-First Thinking

By 小月土星 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

The project models a discipline that pays off at scale: pin down the shape of your data and the boundaries of your state before writing a single component. Skipping this step produces the same `todo` object scattered across five files with conflicting assumptions, a pattern that turns routine changes into archaeology.

Summary

A React + TypeScript Todo application demonstrates architecture-first development by separating concerns into types, a custom Hook for all business logic, and pure UI components. The project defines a `Todo` interface and a `FilterType` literal union before any UI code exists, then encapsulates all state management inside a single `useTodos` Hook. Every state mutation uses functional updates to respect React's reference-comparison rendering, and the Hook returns an object rather than an array so consumers can destructure by name.

The component layer is deliberately left as empty shells — `TodoInput`, `TodoList`, `TodoItem`, `TodoFilter` — which reveals the intended container-presentation split before implementation begins. Data flows unidirectionally: user action triggers a Hook method, `setState` updates the store, and React re-renders only the affected components. The article also flags a deliberate gap: the `filter` state exists but no filtered list is computed yet, a decision point between Hook cohesion and component flexibility.

A restaurant metaphor maps types to recipes, the Hook to a head chef, components to waiters, and `App.tsx` to the manager, reinforcing that each layer minds only its own concern.

Takeaways
All business logic lives in a single custom Hook (`useTodos`), leaving components responsible only for rendering UI.
State mutations use functional updates (`setTodos(prev => ...)`) because React's render bail-out relies on reference equality, not deep comparison.
The Hook returns an object (`{ todos, filter, addTodo, ... }`) rather than an array so consumers can destructure by name and ignore what they don't need.
`clearCompleted` uses `filter` instead of iterative deletion, a declarative approach that avoids index-shift bugs.
`FilterType` is a string literal union (`'all' | 'completed' | 'uncompleted'`), so the compiler rejects invalid filter values at build time.
The component files are intentionally empty shells, proving the architecture was laid out before any UI code was written.
`StrictMode` in `main.tsx` double-invokes initializers and effects in development to surface impure side effects early.
The `filter` state is defined but not yet wired to a filtered list, exposing a real architectural trade-off between Hook cohesion and component flexibility.
Conclusions

Leaving the component files as empty shells is a deliberate pedagogical choice: it forces the reader to see the architecture before the implementation, which is the opposite of how most tutorials operate.

The commented-out `filteredTodos` function is more instructive than a finished feature would be. It surfaces a genuine design tension — compute the derived list inside the Hook for cohesion, or in the component for flexibility — without resolving it prematurely.

Using `Date.now().toString()` for IDs trades a tiny performance cost for guaranteed uniqueness without a dependency, a pragmatic choice that a more dogmatic codebase might over-engineer with UUID libraries.

The restaurant metaphor maps cleanly onto the layers but also reveals what's missing: there is no persistence layer (localStorage, API), which is the next natural boundary to extract.

Concepts & terms
Functional state update
Passing a callback to `setState` that receives the previous state and returns the new state (`setTodos(prev => ...)`). React guarantees `prev` is the latest value, avoiding stale closures, and the new object reference triggers re-rendering.
Literal union type
A TypeScript type that restricts a value to a specific set of string or number literals, such as `'all' | 'completed' | 'uncompleted'`. The compiler rejects any value outside the set, catching typos and invalid states at build time.
Container-presentation pattern
A React component architecture where container components manage state and logic, then pass data down to presentational components that only render UI. The project's `TodoList` (container) and `TodoItem` (presentational) form the embryonic version of this pattern.
Unidirectional data flow
Data in React moves in one direction: parent to child via props. State changes happen through callbacks that bubble up, never through direct mutation of a parent's state by a child. This makes bug tracing a linear reverse-walk rather than a search across multiple mutation points.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗