State, Action, and Reducer
Model features as pure reducers over state.
State, Action, and Reducer is a free Swift Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is The Composable Architecture?
The Composable Architecture, or TCA, is a library for building Swift apps with a consistent, testable structure. It centers on three pieces: State describing your feature's data, Action describing every event that can happen, and a Reducer that evolves state in response to actions.
This unidirectional flow makes features predictable and easy to test.
import ComposableArchitecture
// State holds data, Action describes events,
// a Reducer turns (state, action) into the next state.The @Reducer Macro
A feature is defined as a type annotated with the @Reducer macro. The macro generates supporting code and lets you declare your State, Action, and reducer body together in one cohesive unit.
By convention the type is named after the feature, such as CounterFeature.
import ComposableArchitecture
@Reducer
struct CounterFeature {
// State, Action, and body go here
}Defining State
State is usually a struct that holds all the data a feature needs. Marking it @ObservableState lets SwiftUI observe changes precisely and re-render only what is affected.
Keep state minimal and derived values computed, so there is a single source of truth.
import ComposableArchitecture
@Reducer
struct CounterFeature {
@ObservableState
struct State: Equatable {
var count = 0
}
}Defining Actions
Action is an enum listing every event the feature can handle: user taps, responses, timer ticks, and so on. Each case names something that happened, not a command to mutate state directly.
Clear action names make the feature's behavior self-documenting.
import ComposableArchitecture
@Reducer
struct CounterFeature {
@ObservableState
struct State: Equatable { var count = 0 }
enum Action {
case incrementTapped
case decrementTapped
}
}The Reducer body
The reducer logic lives in a computed body property that returns some ReducerOf<Self>. Inside, you use Reduce with a closure that receives mutable state and the incoming action.
You switch over the action and mutate state accordingly.
import ComposableArchitecture
@Reducer
struct CounterFeature {
@ObservableState
struct State: Equatable { var count = 0 }
enum Action { case incrementTapped, decrementTapped }
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .incrementTapped:
state.count += 1
return .none
case .decrementTapped:
state.count -= 1
return .none
}
}
}
}Returning Effects
Each branch of the reducer must return an Effect. Returning .none means there is no further asynchronous work to perform after the state change.
When you do need side effects, such as a network call, you return an effect that runs that work. You will explore effects in a later lesson.
import ComposableArchitecture
// Every reducer branch returns an Effect.
// No side effect needed? Return .none.
// Need async work? Return an effect describing it.
Reduce { state, action in
state.count += 1
return .none
}State Mutations Are Centralized
A core rule of TCA is that only the reducer mutates state, and only in response to actions. Views never change state directly; they send actions.
This single, central place for mutations is what makes the data flow easy to follow and to test.
import ComposableArchitecture
// Views send actions; the reducer mutates state.
// store.send(.incrementTapped) -> state.count += 1
// No direct state mutation outside the reducer.Modeling User Input in State
Form-like features keep editable fields in State and add actions for changes. Marking properties with bindings lets SwiftUI controls update them through actions in a type-safe way.
Here a feature tracks a text field and a toggle.
import ComposableArchitecture
@Reducer
struct FormFeature {
@ObservableState
struct State: Equatable {
var name = ""
var isSubscribed = false
}
enum Action: BindableAction {
case binding(BindingAction<State>)
}
var body: some ReducerOf<Self> {
BindingReducer()
}
}Actions With Associated Values
Actions can carry data using associated values. This is how you model results coming back, like a loaded item or an error, so the reducer can store them in state.
Here a response action carries the fetched number.
import ComposableArchitecture
@Reducer
struct NumberFeature {
@ObservableState
struct State: Equatable { var value = 0 }
enum Action {
case reloadTapped
case responseReceived(Int)
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .reloadTapped:
return .none
case let .responseReceived(value):
state.value = value
return .none
}
}
}
}Why Equatable State Helps
Conforming State to Equatable lets TCA detect whether state actually changed, which powers precise view updates and exhaustive testing. It is a small requirement with large benefits.
Most state types adopt Equatable for free because their stored properties already are.
import ComposableArchitecture
@ObservableState
struct State: Equatable {
var count = 0
var title = ""
}
// Equatable enables change detection and test assertions.The Shape of a Complete Feature
A finished feature reads top to bottom: the macro, the observable state, the action enum, and the reducer body. Everything about the feature lives in one type, which is what makes TCA features composable and reviewable.
This consistent shape repeats across every feature in an app.
import ComposableArchitecture
@Reducer
struct CounterFeature {
@ObservableState
struct State: Equatable { var count = 0 }
enum Action { case incrementTapped, decrementTapped }
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .incrementTapped: state.count += 1; return .none
case .decrementTapped: state.count -= 1; return .none
}
}
}
}Quick Check: The Three Pieces
Recall the roles of State, Action, and the Reducer in TCA.
Recap: State, Action, and Reducer
You learned the heart of a TCA feature:
@Reducerdefines a feature type containing everything in one place.@ObservableStateon a struct holds the feature's data and enables precise observation.- An
Actionenum lists every event, optionally with associated values. - The reducer
bodyusesReduceto mutate state and return anEffect, with.nonewhen no side effect is needed. - Only the reducer mutates state, keeping data flow predictable.
Next you will connect this feature to SwiftUI with a Store.
import ComposableArchitecture
// Recap: @Reducer + State + Action + body(Reduce)
// One type, one source of truth, predictable updates.Frequently asked questions
Is the “State, Action, and Reducer” lesson free?
Yes — the full text of “State, Action, and Reducer” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.
What will I learn in “State, Action, and Reducer”?
Model features as pure reducers over state. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Swift Academy?
No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “State, Action, and Reducer” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Swift Academy lesson?
Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- State, Action, and Reducer
- The Store and SwiftUI Integration
- Effects and Dependencies
- Composing and Testing Features