-
Notifications
You must be signed in to change notification settings - Fork 1
Async Effects
ChiefVenzox edited this page Jun 5, 2026
·
1 revision
Use an EffectReducer when an action needs to start async work.
Use effects for:
- API requests
- local database reads and writes
- file operations
- timers
- authentication flows
- async validation
Keep reducers responsible for state changes and effects responsible for asynchronous work.
struct AppState: State {
var isLoading = false
var message: String?
}
enum AppAction: Action {
case loadMessage
case messageLoaded(String)
case messageFailed(String)
}let appEffectReducer: EffectReducer<AppState> = { state, action in
guard let action = action as? AppAction else { return .none }
switch action {
case .loadMessage:
state.isLoading = true
state.message = nil
return .run { dispatch in
do {
try await Task.sleep(nanoseconds: 500_000_000)
await dispatch(AppAction.messageLoaded("Loaded"))
} catch {
await dispatch(AppAction.messageFailed(error.localizedDescription))
}
}
case .messageLoaded(let message):
state.isLoading = false
state.message = message
return .none
case .messageFailed(let message):
state.isLoading = false
state.message = message
return .none
}
}@StateObject private var store = Store(
initialState: AppState(),
effectReducer: appEffectReducer
)return .noneUse when no async work is needed.
return .run { dispatch in
let value = try await client.load()
await dispatch(AppAction.loaded(value))
}Use for async work that dispatches follow-up actions.
return .merge(effectA, effectB)Runs multiple effects in order.
- Set loading state before returning the effect.
- Always dispatch success or failure actions.
- Keep network clients injected from outside the reducer.
- Do not mutate state inside the effect closure.
- Keep cancellation-heavy work as a future enhancement until your app needs it.