The Composable Architecture (TCA)
Overview
Use this skill for authoritative guidance on TCA 1.23+. Covers reducer composition, effect management, dependency injection, navigation patterns, testing strategies, bindings, shared state, and performance optimization.
Agent behavior contract (follow these rules)
- •Always use the
@Reducermacro — never hand-writeReducerconformances. - •Use
@ObservableStateon allStatestructs — never useWithViewStoreorViewStore(deprecated). - •Prefer
@Dependencyfor all external interactions — never reach for singletons or global state. - •Default to stack-based navigation for list-to-detail flows and tree-based for modals/alerts.
- •Write exhaustive
TestStoretests by default; use non-exhaustive only for integration tests. - •Keep
Reducebody lightweight — offload async/CPU work to.runeffects. - •Use
@DependencyClientmacro for client definitions — never hand-write unimplemented defaults. - •Prefer helper methods on the reducer over action ping-pong for sharing logic.
- •Always use the latest non-deprecated TCA APIs for the version in use. Check the project's resolved TCA version and avoid any API marked
@available(*, deprecated, ...). In particular, never use@Reducer(state:action:)— use extensions for conformances instead.
First 60 seconds (triage template)
- •Clarify the goal: new feature, navigation, testing, debugging, performance, or review.
- •Collect minimal facts:
- •TCA version (1.23+ assumed)
- •Swift version and concurrency mode
- •Whether using stack-based or tree-based navigation
- •Whether the issue involves effects, state, or view integration
- •Branch quickly:
- •new feature -> reducers + effects + dependencies
- •navigation issues -> navigation reference
- •test failures -> testing reference
- •performance -> performance reference
- •form/binding heavy -> bindings reference
- •cross-feature state -> shared state reference
Common pitfalls -> next best move
- •Action ping-pong between parent/child for shared logic -> extract helper method on reducer.
- •
WithViewStore/ViewStoreusage -> migrate to@ObservableState+ directstore.propertyaccess. - •Force-unwrapping dependency in
liveValue-> use@DependencyinsideliveValueclosure. - •Testing non-deterministic effects -> inject controllable clocks and UUID generators.
- •Navigation state not updating -> verify
.ifLet/.forEachwiring in reducer body. - •Binding compile errors -> check
@Bindable var storein view andBindableActionon Action. - •Shared state not syncing -> verify
@Sharedreference passing with$prefix.
Verification checklist
- •Confirm
@Reducermacro is applied to all reducer structs. - •Confirm
@ObservableStateis on all State types. - •Confirm all external dependencies use
@Dependency. - •Confirm navigation uses
.ifLet(tree) or.forEach(stack) in reducer body. - •Confirm tests use
TestStorewith proper dependency overrides. - •Confirm effects capture state values, not
stateitself (e.g.,[id = state.id]). - •Confirm no
ViewStore,WithViewStore, orIfLetStoreusage (all deprecated).
References
- •
references/_index.md— navigation index with quick links by problem - •
references/reducers.md - •
references/effects.md - •
references/dependencies.md - •
references/navigation.md - •
references/testing.md - •
references/bindings.md - •
references/shared-state.md - •
references/performance.md