Structures
The following structures are available globally.
-
A reducer describes how to evolve the current state of an application to the next state, given an action, and describes what
Effect
s should be executed later by the store, if any.Reducers have 3 generics:
State
: A type that holds the current state of the applicationAction
: A type that holds all possible actions that cause the state of the application to change.Environment
: A type that holds all dependencies needed in order to produceEffect
s, such as API clients, analytics clients, random number generators, etc.
Note
The thread on which effects output is important. An effect’s output is immediately sent back into the store, and
Store
is not thread safe. This means all effects must receive values on the same thread, and if theStore
is being used to drive UI then all output must be on the main thread. You can use thePublisher
methodreceive(on:)
for make the effect output its values on the thread of your choice.Declaration
Swift
public struct Reducer<State, Action, Environment>
-
An environment for debug-printing reducers.
See moreDeclaration
Swift
public struct DebugEnvironment
-
The
Effect
type encapsulates a unit of work that can be run in the outside world, and can feed data back to theStore
. It is the perfect place to do side effects, such as network requests, saving/loading from disk, creating timers, interacting with dependencies, and more.Effects are returned from reducers so that the
Store
can perform the effects after the reducer is done running. It is important to note thatStore
is not thread safe, and so all effects must receive values on the same thread, and if the store is being used to drive UI then it must receive values on the main thread.An effect simply wraps a
See morePublisher
value and provides some convenience initializers for constructing some common types of effects.Declaration
Swift
public struct Effect<Output, Failure> : Publisher where Failure : Error
extension Effect: CustomDebugOutputConvertible
-
A publisher of store state.
See moreDeclaration
Swift
@dynamicMemberLookup public struct StorePublisher<State> : Publisher
-
A data type that describes the state of an alert that can be shown to the user. The
Action
generic is the type of actions that can be sent from tapping on a button in the alert.This type can be used in your application’s state in order to control the presentation or dismissal of alerts. It is preferrable to use this API instead of the default SwiftUI API for alerts because SwiftUI uses 2-way bindings in order to control the showing and dismissal of alerts, and that does not play nicely with the Composable Architecture. The library requires that all state mutations happen by sending an action so that a reducer can handle that logic, which greatly simplifies how data flows through your application, and gives you instant testability on all parts of your application.
To use this API, you model all the alert actions in your domain’s action enum:
enum AppAction: Equatable { case cancelTapped case confirmTapped case deleteTapped // Your other actions }
And you model the state for showing the alert in your domain’s state, and it can start off
nil
:struct AppState: Equatable { var alert = AlertState<AppAction>? // Your other state }
Then, in the reducer you can construct an
AlertState
value to represent the alert you want to show to the user:let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in switch action case .cancelTapped: state.alert = nil return .none case .confirmTapped: state.alert = nil // Do deletion logic... case .deleteTapped: state.alert = .init( title: "Delete", message: "Are you sure you want to delete this? It cannot be undone.", primaryButton: .default("Confirm", send: .confirmTapped), secondaryButton: .cancel() ) return .none } }
And then, in your view you can use the
.alert(_:send:dismiss:)
method onView
in order to present the alert in a way that works best with the Composable Architecture:Button("Delete") { viewStore.send(.deleteTapped) } .alert( self.store.scope(state: \.alert), dismiss: .cancelTapped )
This makes your reducer in complete control of when the alert is shown or dismissed, and makes it so that any choice made in the alert is automatically fed back into the reducer so that you can handle its logic.
Even better, you can instantly write tests that your alert behavior works as expected:
See morelet store = TestStore( initialState: AppState(), reducer: appReducer, environment: .mock ) store.assert( .send(.deleteTapped) { $0.alert = .init( title: "Delete", message: "Are you sure you want to delete this? It cannot be undone.", primaryButton: .default("Confirm", send: .confirmTapped), secondaryButton: .cancel(send: .cancelTapped) ) }, .send(.deleteTapped) { $0.alert = nil // Also verify that delete logic executed correctly } )
Declaration
Swift
public struct AlertState<Action>
extension AlertState: Equatable where Action: Equatable
extension AlertState: Hashable where Action: Hashable
extension AlertState: Identifiable where Action: Hashable
-
A structure that computes views on demand from a store on a collection of data.
See moreDeclaration
Swift
public struct ForEachStore<EachState, EachAction, Data, ID, Content>: DynamicViewContent where Data: Collection, ID: Hashable, Content: View
-
A wrapper around a value and a hashable identifier that conforms to identifiable.
See moreDeclaration
Swift
@dynamicMemberLookup public struct Identified<ID, Value> : Identifiable where ID : Hashable
extension Identified: Decodable where ID: Decodable, Value: Decodable
extension Identified: Encodable where ID: Encodable, Value: Encodable
extension Identified: Equatable where Value: Equatable
extension Identified: Hashable where Value: Hashable
-
An array of elements that can be identified by a given key path.
A useful container of state that is intended to interface with
SwiftUI.ForEach
. For example, your application may model a counter in an identifiable fashion:struct CounterState: Identifiable { let id: UUID var count = 0 } enum CounterAction { case incr, decr } let counterReducer = Reducer<CounterState, CounterAction, Void> { ... }
This domain can be pulled back to a larger domain with the
forEach
method:struct AppState { var counters = IdentifiedArray<Int>(id: \.self) } enum AppAction { case counter(id: UUID, action: CounterAction) } let appReducer = counterReducer.forEach( state: \AppState.counters, action: /AppAction.counter(id:action:), environment: { $0 } )
And then SwiftUI can work with this array of identified elements in a list view:
See morestruct AppView: View { let store: Store<AppState, AppAction> var body: some View { List { ForEachStore( self.store.scope(state: \.counters, action: AppAction.counter(id:action)) content: CounterView.init(store:) ) } } }
Declaration
Swift
public struct IdentifiedArray<ID, Element>: MutableCollection, RandomAccessCollection where ID: Hashable
extension IdentifiedArray: CustomDebugStringConvertible
extension IdentifiedArray: CustomReflectable
extension IdentifiedArray: CustomStringConvertible
extension IdentifiedArray: Decodable where Element: Decodable & Identifiable, ID == Element.ID
extension IdentifiedArray: Encodable where Element: Encodable
extension IdentifiedArray: Equatable where Element: Equatable
extension IdentifiedArray: Hashable where Element: Hashable
extension IdentifiedArray: ExpressibleByArrayLiteral where Element: Identifiable, ID == Element.ID
extension IdentifiedArray: RangeReplaceableCollection where Element: Identifiable, ID == Element.ID
-
A view that safely unwraps a store of optional state in order to show one of two views.
When the underlying state is non-
nil
, thethen
closure will be performed with aStore
that holds onto non-optional state, and otherwise theelse
closure will be performed.This is useful for deciding between two views to show depending on an optional piece of state:
IfLetStore( store.scope(state: \SearchState.results, action: SearchAction.results), then: SearchResultsView.init(store:), else: Text("Loading search results...") )
And for performing navigation when a piece of state becomes non-
nil
:
See moreNavigationLink( destination: IfLetStore( self.store.scope(state: \.detail, action: AppAction.detail), then: DetailView.init(store:) ), isActive: viewStore.binding( get: \.isGameActive, send: { $0 ? .startButtonTapped : .detailDismissed } ) ) { Text("Start!") }
Declaration
Swift
public struct IfLetStore<State, Action, Content> : View where Content : View
-
A structure that transforms a store into an observable view store in order to compute views from store state.
Due to a bug in SwiftUI, there are times that use of this view can interfere with some core views provided by SwiftUI. The known problematic views are:
- If a
GeometryReader
is used inside aWithViewStore
it will not receive state updates correctly. To work around you either need to reorder the views so thatGeometryReader
wraps theWithViewStore
, or, if that is not possible, then you must hold onto an explicit@ObservedObject var viewStore: ViewStore<State, Action>
in your view in lieu of using this helper (see here). - If you create a
Stepper
via theStepper.init(onIncrement:onDecrement:label:)
initializer inside aWithViewStore
it will behave erratically. To work around you should use the initializer that takes a binding (see here).
Declaration
Swift
public struct WithViewStore<State, Action, Content> : View where Content : View
extension WithViewStore: DynamicViewContent where State: Collection, Content: DynamicViewContent
- If a