composable-architecture
The Composable Architecture (TCA)
TCA provides architecture for building complex, testable features through composable reducers, centralized state management, and side effect handling. The core principle: predictable state evolution with clear dependencies and testable effects.
Reference Loading Guide
ALWAYS load reference files if there is even a small chance the content may be required. It's better to have the context than to miss a pattern or make a mistake.
| Reference | Load When |
|---|---|
| Reducer Structure | Creating new reducers, setting up @Reducer, State, Action, or @ViewAction |
| Views - Binding | Using @Bindable, two-way bindings, store.send(), or .onAppear/.task |
| Views - Composition | Using ForEach with stores, scoping to child features, or optional children |
| Navigation - Basics | Setting up NavigationStack, path reducers, pushing/popping, or programmatic dismiss |
| Navigation - Advanced | Deep linking, recursive navigation, or combining NavigationStack with sheets |
| Shared State | Using @Shared, .appStorage, .withLock, or sharing state between features |
| Dependencies | Creating @DependencyClient, using @Dependency, or setting up test dependencies |
| Effects | Using .run, .send, .merge, timers, effect cancellation, or async work |
| Presentation | Using @Presents, AlertState, sheets, popovers, or the Destination pattern |
| Testing - Fundamentals | Setting up test suites, makeStore helpers, or understanding Equatable requirements |
| Testing - Patterns | Testing actions, state changes, dependencies, errors, or presentations |
| Testing - Advanced | Using TestClock, keypath matching, exhaustivity = .off, or time-based tests |
| Testing - Utilities | Test data factories, LockIsolated, ConfirmationDialogState testing, or @Shared testing |
| Performance | Optimizing state updates, high-frequency actions, memory, or store scoping |
Common Mistakes
-
Over-modularizing features — Breaking features into too many small reducers makes state management harder and adds composition overhead. Keep related state and actions together unless there's genuine reuse.
-
Mismanaging effect lifetimes — Forgetting to cancel effects when state changes leads to stale data, duplicate requests, or race conditions. Use
.concatenatefor sequential effects and.cancelwhen appropriate. -
Navigation state in wrong places — Putting navigation state in child reducers instead of parent causes unnecessary view reloads and state inconsistencies. Navigation state belongs in the feature that owns the navigation structure.
-
Testing without TestStore exhaustivity — Skipping TestStore assertions for "simple" effects or "obvious" state changes means you miss bugs. Use exhaustivity checking religiously; it catches regressions early.
-
Mixing async/await with Effects incorrectly — Converting async/await to
.runeffects without proper cancellation or error handling loses isolation guarantees. Wrap async operations carefully in.runwithyieldstatements.