Skip to content
AB

The Composable Architecture in a Real Application

Use state, actions, reducers, effects, and dependencies to make complex SwiftUI behavior explicit and testable.

Ashish BhakhandPublished

TCA examples often show a counter. Its value appears when one screen loads local data, refreshes an API, starts a cancellable stream, presents a child feature, and handles retries.

Code
@Reducer
struct ChatFeature {
    @ObservableState
    struct State: Equatable {
        var messages: [Message] = []
        var input = ""
        var isStreaming = false
        var error: AppError?
    }

    enum Action {
        case sendTapped
        case streamResponse(Result<ChatEvent, AppError>)
        case cancelTapped
    }
}

State contains everything that can affect rendering. Actions describe events, not setter methods. The reducer decides the next state and which effects begin.

That gives debugging a useful question: “Which action produced this state?” Network callbacks and view lifecycle events enter through the same vocabulary.

Effects must have identities when newer work replaces older work. A chat stream, search request, or session load should be cancellable without cancelling unrelated tasks.

TCA does add types and ceremony. I use it where a feature has concurrent effects, navigation state, retry behavior, or several child flows. A static settings screen may not need the same weight.

The goal is not to put every line in a reducer. Formatting and pure calculations remain ordinary Swift functions. Persistence and APIs remain dependencies. The reducer owns coordination: the part most likely to become implicit and untestable in a large SwiftUI view.

← Back to all writing