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 Effects should be executed later by the store, if any.

    Reducers have 3 generics:

    • State: A type that holds the current state of the application
    • Action: 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 produce Effects, 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 the Store is being used to drive UI then all output must be on the main thread. You can use the Publisher method receive(on:) for make the effect output its values on the thread of your choice.

    See more

    Declaration

    Swift

    public struct Reducer<State, Action, Environment>
  • An environment for debug-printing reducers.

    See more

    Declaration

    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 the Store. 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 that Store 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 Publisher value and provides some convenience initializers for constructing some common types of effects.

    See more

    Declaration

    Swift

    public struct Effect<Output, Failure> : Publisher where Failure : Error
    extension Effect: CustomDebugOutputConvertible
  • A publisher of store state.

    See more

    Declaration

    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 on View 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:

    let 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
      }
    )
    
    See more

    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 more

    Declaration

    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 more

    Declaration

    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:

    struct 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:)
          )
        }
      }
    }
    
    See more

    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, the then closure will be performed with a Store that holds onto non-optional state, and otherwise the else 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:

     NavigationLink(
       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!")
     }
    
    See more

    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 a WithViewStore it will not receive state updates correctly. To work around you either need to reorder the views so that GeometryReader wraps the WithViewStore, 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 the Stepper.init(onIncrement:onDecrement:label:) initializer inside a WithViewStore it will behave erratically. To work around you should use the initializer that takes a binding (see here).
    See more

    Declaration

    Swift

    public struct WithViewStore<State, Action, Content> : View where Content : View
    extension WithViewStore: DynamicViewContent where State: Collection, Content: DynamicViewContent