swift-composable-architecture

You are an expert in The Composable Architecture (TCA) by Point-Free. Help developers write correct, testable, and composable Swift code following TCA patterns.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "swift-composable-architecture" with this command: npx skills add hocgin/agent-skills/hocgin-agent-skills-swift-composable-architecture

You are an expert in The Composable Architecture (TCA) by Point-Free. Help developers write correct, testable, and composable Swift code following TCA patterns.

Core Principles

  • Unidirectional data flow: Action → Reducer → State → View

  • State as value types: Simple, equatable structs

  • Effects are explicit: Side effects return from reducers as Effect values

  • Composition over inheritance: Small, isolated, recombinable modules

  • Testability first: Every feature testable with TestStore

The Four Building Blocks

  • State – Data for UI and logic (@ObservableState struct )

  • Action – All events: user actions, effects, delegates (enum with @CasePathable )

  • Reducer – Pure function evolving state, returning effects (@Reducer macro )

  • Store – Runtime connecting state, reducer, and views (StoreOf<Feature> )

Feature Structure

import SwiftTCACore

public extension SwiftTCACore { enum Feature {} }

// MARK: - 视图 extension SwiftTCACore.Feature { struct FeatureView: View { let store: StoreOf<Feature>

    var body: some View {
        List(store.items) { item in
            Text(item.title)
        }
        .onAppear { store.send(.onAppear) }
    }
}

}

extension SwiftTCACore.Feature.FeatureView { @Reducer public struct Store { // MARK: - 状态 @ObservableState struct State: Equatable { var items: IdentifiedArrayOf<Item> = [] var isLoading = false }

    // MARK: - 事件
    @CasePathable enum Action {
        case onAppear
        case itemsResponse (Result&#x3C;[Item], Error>)
        case delegate (Delegate)
        @CasePathable enum Delegate {
            case itemSelected (Item)
        }
    }

    @Dependency(\.apiClient) var apiClient

    var body: some ReducerOf&#x3C;Self> {
        Reduce(core)
    }

    func core(into state: inout State, action: Action) -> Effect&#x3C;Action> {
        switch action {
        case .onAppear:
            state.isLoading = true
            return .run {
                send in
                await send(.itemsResponse(Result {
                    try await apiClient.fetchItems()
                }))
            }
        case .itemsResponse(.success(let items)):
            state.isLoading = false
            state.items = IdentifiedArray(uniqueElements: items)
            return .none
        case .itemsResponse(.failure):
            state.isLoading = false
            return .none
        case .delegate:
            return .none
        }
    }
}

}

typealias FeatureView = SwiftTCACore.Feature.FeatureView

extension FeatureView.Store.State: @unchecked @retroactive Sendable { static let default: SwiftTCACore.Feature.FeatureView.Store.State = .init() }

Effects

Pattern Use Case

.none

Synchronous state change, no side effect

.run { send in }

Async work, send actions back

.cancellable(id:)

Long-running/replaceable effects

.cancel(id:)

Cancel a running effect

.merge(...)

Run multiple effects in parallel

.concatenate(...)

Run effects sequentially

Cancellation

enum CancelID { case search }

case .searchQueryChanged(let query): return .run { send in try await clock.sleep(for: .milliseconds(300)) await send(.searchResponse(try await api.search(query))) } .cancellable(id: CancelID.search, cancelInFlight: true)

cancelInFlight: true auto-cancels previous effect with same ID.

Dependencies

Built-in Dependencies

@Dependency(.uuid) , @Dependency(.date) , @Dependency(.continuousClock) , @Dependency(.mainQueue)

Custom Dependencies

  • Define client struct with closures

  • Conform to DependencyKey with liveValue , testValue , previewValue

  • Extend DependencyValues with computed property

  • Use @Dependency(.yourClient) in reducer

Test override: withDependencies { $0.apiClient.fetch = { .mock } }

Composition

Child Features

Use Scope to embed children:

var body: some ReducerOf<Self> { Scope(state: .child, action: .child) { ChildFeature() } Reduce { state, action in ... } }

View: ChildView(store: store.scope(state: .child, action: .child))

Collections

Use IdentifiedArrayOf<ChildFeature.State> with .forEach(.items, action: .items) { ChildFeature() }

Navigation

Tree-Based (sheets, alerts, single drill-down)

  • Model with optional state: @Presents var detail: DetailFeature.State?

  • Action: case detail(PresentationAction<DetailFeature.Action>)

  • Reducer: .ifLet(.$detail, action: .detail) { DetailFeature() }

  • View: .sheet(item: $store.scope(state: .detail, action: .detail))

Stack-Based (NavigationStack, deep linking)

  • Model with StackState<Path.State> and StackActionOf<Path>

  • Define @Reducer enum Path { case detail(DetailFeature) ... }

  • Reducer: .forEach(.path, action: .path)

  • View: NavigationStack(path: $store.scope(state: .path, action: .path))

Delegates

Child emits delegate actions for outcomes; parent responds without child knowing parent's implementation.

Testing

TestStore Basics

let store = TestStore(initialState: Feature.State()) { Feature() } withDependencies: { $0.apiClient.fetch = { .mock } }

await store.send(.onAppear) { $0.isLoading = true } await store.receive(.itemsResponse.success) { $0.isLoading = false; $0.items = [.mock] }

Key Patterns

  • Override dependencies - never hit real APIs in tests

  • Assert all state changes - mutations in trailing closure

  • Receive all effects - TestStore enforces exhaustivity

  • TestClock - control time-based effects with clock.advance(by:)

  • Integration tests - test composed parent+child features together

Higher-Order Reducers

For cross-cutting concerns (logging, analytics, metrics, feature flags):

extension Reducer { func analytics(_ tracker: AnalyticsClient) -> some ReducerOf<Self> { Reduce { state, action in tracker.track(action) return self.reduce(into: &state, action: action) } } }

Modern TCA (2025+)

  • @Reducer macro generates boilerplate

  • @ObservableState replaces manual WithViewStore

  • @CasePathable enables key path syntax for actions (.action.child )

  • @Dependency with built-in clients (Clock, UUID, Date)

  • @MainActor on State when SwiftUI requires it

  • Direct store access in views (no more viewStore )

Critical Rules

DO:

  • Keep reducers pure - side effects through Effect only

  • Use IdentifiedArray for collections

  • Test state transitions and effect outputs

  • Use delegates for child→parent communication

DO NOT:

  • Mutate state outside reducers

  • Call async code directly in reducers

  • Create stores inside views

  • Use @State /@StateObject for TCA-managed state

  • Skip receiving actions in tests

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

swift-composable-architecture

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

swift-composable-architecture

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

vercel-react-best-practices

React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.

Repository Source
23K213.2K
vercel