ComposableArchitecture Documentation Beta

Structure Reducer

public struct Reducer<State, Action, Environment>

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.

Initializers

init(_:​)

public init(_ reducer: @escaping (inout State, Action, Environment) -> Effect<Action, Never>)

Initializes a reducer from a simple reducer function signature.

The reducer takes three arguments: state, action and environment. The state is inout so that you can make any changes to it directly inline. The reducer must return an effect, which typically would be constructed by using the dependencies inside the environment value. If no effect needs to be executed, a .none effect can be returned.

For example:

struct MyState { var count = 0, text = "" }
enum MyAction { case buttonTapped, textChanged(String) }
struct MyEnvironment { var analyticsClient: AnalyticsClient }

let myReducer = Reducer<MyState, MyAction, MyEnvironment> { state, action, environment in
  switch action {
  case .buttonTapped:
    state.count += 1
    return environment.analyticsClient.track("Button Tapped")

  case .textChanged(let text):
    state.text = text
    return .none
  }
}

Parameters

reducer @escaping (inout State, Action, Environment) -> Effect<Action, Never>

A function signature that takes state, action and environment.

Properties

optional

var optional: Reducer<State?, Action, Environment>

empty

var empty: Reducer

A reducer that performs no state mutations and returns no effects.

Methods

debug(_:​action​Format:​environment:​)

public func debug(_ prefix: String = "", actionFormat: ActionFormat = .prettyPrint, environment toDebugEnvironment: @escaping (Environment) -> DebugEnvironment = { _ in
      DebugEnvironment()
    }) -> Reducer

Prints debug messages describing all received actions and state mutations.

Printing is only done in debug (#if DEBUG) builds.

Parameters

prefix String

A string with which to prefix all debug messages.

to​Debug​Environment @escaping (Environment) -> Debug​Environment

A function that transforms an environment into a debug environment by describing a print function and a queue to print from. Defaults to a function that ignores the environment and returns a default DebugEnvironment that uses Swift's print function and a background queue.

Returns

A reducer that prints debug messages for all received actions.

debug​Actions(_:​action​Format:​environment:​)

public func debugActions(_ prefix: String = "", actionFormat: ActionFormat = .prettyPrint, environment toDebugEnvironment: @escaping (Environment) -> DebugEnvironment = { _ in
      DebugEnvironment()
    }) -> Reducer

Prints debug messages describing all received actions.

Printing is only done in debug (#if DEBUG) builds.

Parameters

prefix String

A string with which to prefix all debug messages.

to​Debug​Environment @escaping (Environment) -> Debug​Environment

A function that transforms an environment into a debug environment by describing a print function and a queue to print from. Defaults to a function that ignores the environment and returns a default DebugEnvironment that uses Swift's print function and a background queue.

Returns

A reducer that prints debug messages for all received actions.

debug(_:​state:​action:​action​Format:​environment:​)

public func debug<LocalState, LocalAction>(_ prefix: String = "", state toLocalState: @escaping (State) -> LocalState, action toLocalAction: CasePath<Action, LocalAction>, actionFormat: ActionFormat = .prettyPrint, environment toDebugEnvironment: @escaping (Environment) -> DebugEnvironment = { _ in
      DebugEnvironment()
    }) -> Reducer

Prints debug messages describing all received local actions and local state mutations.

Printing is only done in debug (#if DEBUG) builds.

Parameters

prefix String

A string with which to prefix all debug messages.

to​Local​State @escaping (State) -> Local​State

A function that filters state to be printed.

to​Local​Action Case​Path<Action, Local​Action>

A case path that filters actions that are printed.

to​Debug​Environment @escaping (Environment) -> Debug​Environment

A function that transforms an environment into a debug environment by describing a print function and a queue to print from. Defaults to a function that ignores the environment and returns a default DebugEnvironment that uses Swift's print function and a background queue.

Returns

A reducer that prints debug messages for all received actions.

signpost(_:​log:​)

public func signpost(_ prefix: String = "", log: OSLog = OSLog(
      subsystem: "co.pointfree.composable-architecture",
      category: "Reducer Instrumentation"
    )) -> Self

Instruments the reducer with signposts. Each invocation of the reducer will be measured by an interval, and the lifecycle of its effects will be measured with interval and event signposts.

To use, build your app for Instruments (⌘I), create a blank instrument, and then use the "+" icon at top right to add the signpost instrument. Start recording your app (red button at top left) and then you should see timing information for every action sent to the store and every effect executed.

Effect instrumentation can be particularly useful for inspecting the lifecycle of long-living effects. For example, if you start an effect (e.g. a location manager) in onAppear and forget to tear down the effect in onDisappear, it will clearly show in Instruments that the effect never completed.

Parameters

prefix String

A string to print at the beginning of the formatted message for the signpost.

log OSLog

An OSLog to use for signposts.

Returns

A reducer that has been enhanced with instrumentation.

debug(prefix:​environment:​)

@available(*, unavailable, renamed: "debug(_:environment:)") public func debug(prefix: String, environment toDebugEnvironment: @escaping (Environment) -> DebugEnvironment = { _ in
      DebugEnvironment()
    }) -> Reducer

debug​Actions(prefix:​environment:​)

@available(*, unavailable, renamed: "debugActions(_:environment:)") public func debugActions(prefix: String, environment toDebugEnvironment: @escaping (Environment) -> DebugEnvironment = { _ in
      DebugEnvironment()
    }) -> Reducer

debug(prefix:​state:​action:​environment:​)

@available(*, unavailable, renamed: "debug(_:state:action:environment:)") public func debug<LocalState, LocalAction>(prefix: String, state toLocalState: @escaping (State) -> LocalState, action toLocalAction: CasePath<Action, LocalAction>, environment toDebugEnvironment: @escaping (Environment) -> DebugEnvironment = { _ in
      DebugEnvironment()
    }) -> Reducer

combine(_:​)

public static func combine(_ reducers: Reducer) -> Reducer

Combines many reducers into a single one by running each one on state in order, and merging all of the effects.

It is important to note that the order of combining reducers matter. Combining reducerA with reducerB is not necessarily the same as combining reducerB with reducerA.

This can become an issue when working with reducers that have overlapping domains. For example, if reducerA embeds the domain of reducerB and reacts to its actions or modifies its state, it can make a difference if reducerA chooses to modify reducerB's state before or after reducerB runs.

This is perhaps most easily seen when working with optional reducers, where the parent domain may listen to the child domain and nil out its state. If the parent reducer runs before the child reducer, then the child reducer will not be able to react to its own action.

Similar can be said for a forEach reducer. If the parent domain modifies the child collection by moving, removing, or modifying an element before the forEach reducer runs, the forEach reducer may perform its action against the wrong element, an element that no longer exists, or an element in an unexpected state.

Running a parent reducer before a child reducer can be considered an application logic error, and can produce assertion failures. So you should almost always combine reducers in order from child to parent domain.

Here is an example of how you should combine an optional reducer with a parent domain:

let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(
  // Combined before parent so that it can react to `.dismiss` while state is non-`nil`.
  childReducer.optional().pullback(
    state: \.child,
    action: /ParentAction.child,
    environment: { $0.child }
  ),
  // Combined after child so that it can `nil` out child state upon `.child(.dismiss)`.
  Reducer { state, action, environment in
    switch action
    case .child(.dismiss):
      state.child = nil
      return .none
    ...
    }
  },
)

Parameters

reducers Reducer

A list of reducers.

Returns

A single reducer.

combine(_:​)

public static func combine(_ reducers: [Reducer]) -> Reducer

Combines many reducers into a single one by running each one on state in order, and merging all of the effects.

It is important to note that the order of combining reducers matter. Combining reducerA with reducerB is not necessarily the same as combining reducerB with reducerA.

This can become an issue when working with reducers that have overlapping domains. For example, if reducerA embeds the domain of reducerB and reacts to its actions or modifies its state, it can make a difference if reducerA chooses to modify reducerB's state before or after reducerB runs.

This is perhaps most easily seen when working with optional reducers, where the parent domain may listen to the child domain and nil out its state. If the parent reducer runs before the child reducer, then the child reducer will not be able to react to its own action.

Similar can be said for a forEach reducer. If the parent domain modifies the child collection by moving, removing, or modifying an element before the forEach reducer runs, the forEach reducer may perform its action against the wrong element, an element that no longer exists, or an element in an unexpected state.

Running a parent reducer before a child reducer can be considered an application logic error, and can produce assertion failures. So you should almost always combine reducers in order from child to parent domain.

Here is an example of how you should combine an optional reducer with a parent domain:

let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(
  // Combined before parent so that it can react to `.dismiss` while state is non-`nil`.
  childReducer.optional().pullback(
    state: \.child,
    action: /ParentAction.child,
    environment: { $0.child }
  ),
  // Combined after child so that it can `nil` out child state upon `.child(.dismiss)`.
  Reducer { state, action, environment in
    switch action
    case .child(.dismiss):
      state.child = nil
      return .none
    ...
    }
  },
)

Parameters

reducers [Reducer]

An array of reducers.

Returns

A single reducer.

combined(with:​)

public func combined(with other: Reducer) -> Reducer

Combines many reducers into a single one by running each one on state in order, and merging all of the effects.

It is important to note that the order of combining reducers matter. Combining reducerA with reducerB is not necessarily the same as combining reducerB with reducerA.

This can become an issue when working with reducers that have overlapping domains. For example, if reducerA embeds the domain of reducerB and reacts to its actions or modifies its state, it can make a difference if reducerA chooses to modify reducerB's state before or after reducerB runs.

This is perhaps most easily seen when working with optional reducers, where the parent domain may listen to the child domain and nil out its state. If the parent reducer runs before the child reducer, then the child reducer will not be able to react to its own action.

Similar can be said for a forEach reducer. If the parent domain modifies the child collection by moving, removing, or modifying an element before the forEach reducer runs, the forEach reducer may perform its action against the wrong element, an element that no longer exists, or an element in an unexpected state.

Running a parent reducer before a child reducer can be considered an application logic error, and can produce assertion failures. So you should almost always combine reducers in order from child to parent domain.

Here is an example of how you should combine an optional reducer with a parent domain:

let parentReducer: Reducer<ParentState, ParentAction, ParentEnvironment> =
  // Run before parent so that it can react to `.dismiss` while state is non-`nil`.
  childReducer
    .optional()
    .pullback(
      state: \.child,
      action: /ParentAction.child,
      environment: { $0.child }
    )
    // Combined after child so that it can `nil` out child state upon `.child(.dismiss)`.
    .combined(
      with: Reducer { state, action, environment in
        switch action
        case .child(.dismiss):
          state.child = nil
          return .none
        ...
        }
      }
    )

Parameters

other Reducer

Another reducer.

Returns

A single reducer.

pullback(state:​action:​environment:​)

public func pullback<GlobalState, GlobalAction, GlobalEnvironment>(state toLocalState: WritableKeyPath<GlobalState, State>, action toLocalAction: CasePath<GlobalAction, Action>, environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>

Transforms a reducer that works on local state, action and environment into one that works on global state, action and environment. It accomplishes this by providing 3 transformations to the method:

  • A writable key path that can get/set a piece of local state from the global state.

  • A case path that can extract/embed a local action into a global action.

  • A function that can transform the global environment into a local environment.

This operation is important for breaking down large reducers into small ones. When used with the combine operator you can define many reducers that work on small pieces of domain, and then pull them back and combine them into one big reducer that works on a large domain.

// Global domain that holds a local domain:
struct AppState { var settings: SettingsState, /* rest of state */ }
enum AppAction { case settings(SettingsAction), /* other actions */ }
struct AppEnvironment { var settings: SettingsEnvironment, /* rest of dependencies */ }

// A reducer that works on the local domain:
let settingsReducer = Reducer<SettingsState, SettingsAction, SettingsEnvironment> { ... }

// Pullback the settings reducer so that it works on all of the app domain:
let appReducer: Reducer<AppState, AppAction, AppEnvironment> = .combine(
  settingsReducer.pullback(
    state: \.settings,
    action: /AppAction.settings,
    environment: { $0.settings }
  ),

  /* other reducers */
)

Parameters

to​Local​State Writable​Key​Path<Global​State, State>

A key path that can get/set State inside GlobalState.

to​Local​Action Case​Path<Global​Action, Action>

A case path that can extract/embed Action from GlobalAction.

to​Local​Environment @escaping (Global​Environment) -> Environment

A function that transforms GlobalEnvironment into Environment.

Returns

A reducer that works on GlobalState, GlobalAction, GlobalEnvironment.

optional(_:​_:​)

public func optional(_ file: StaticString = #file, _ line: UInt = #line) -> Reducer<
    State?, Action, Environment
  >

Transforms a reducer that works on non-optional state into one that works on optional state by only running the non-optional reducer when state is non-nil.

Often used in tandem with pullback to transform a reducer on a non-optional local domain into a reducer that can be combined with a reducer on a global domain that contains some optional local domain:

// Global domain that holds an optional local domain:
struct AppState { var modal: ModalState? }
enum AppAction { case modal(ModalAction) }
struct AppEnvironment { var mainQueue: AnySchedulerOf<DispatchQueue> }

// A reducer that works on the non-optional local domain:
let modalReducer = Reducer<ModalState, ModalAction, ModalEnvironment { ... }

// Pullback the local modal reducer so that it works on all of the app domain:
let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
  modalReducer.optional().pullback(
    state: \.modal,
    action: /AppAction.modal,
    environment: { ModalEnvironment(mainQueue: $0.mainQueue) }
  ),
  Reducer { state, action, environment in
    ...
  }
)

Take care when combining optional reducers into parent domains, as order matters. Always combine optional reducers before parent reducers that can nil out the associated optional state.

  • See also: IfLetStore, a SwiftUI helper for transforming a store on optional state into a store on non-optional state.

  • See also: Store.ifLet, a UIKit helper for doing imperative work with a store on optional state.

for​Each(state:​action:​environment:​_:​_:​)

public func forEach<GlobalState, GlobalAction, GlobalEnvironment>(state toLocalState: WritableKeyPath<GlobalState, [State]>, action toLocalAction: CasePath<GlobalAction, (Int, Action)>, environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment, _ file: StaticString = #file, _ line: UInt = #line) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>

A version of pullback that transforms a reducer that works on an element into one that works on a collection of elements.

// Global domain that holds a collection of local domains:
struct AppState { var todos: [Todo] }
enum AppAction { case todo(index: Int, action: TodoAction) }
struct AppEnvironment { var mainQueue: AnySchedulerOf<DispatchQueue> }

// A reducer that works on a local domain:
let todoReducer = Reducer<Todo, TodoAction, TodoEnvironment> { ... }

// Pullback the local todo reducer so that it works on all of the app domain:
let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
  todoReducer.forEach(
    state: \.todos,
    action: /AppAction.todo(index:action:),
    environment: { _ in TodoEnvironment() }
  ),
  Reducer { state, action, environment in
    ...
  }
)

Take care when combining forEach reducers into parent domains, as order matters. Always combine forEach reducers before parent reducers that can modify the collection.

Parameters

to​Local​State Writable​Key​Path<Global​State, [State]>

A key path that can get/set an array of State elements inside. GlobalState.

to​Local​Action Case​Path<Global​Action, (Int, Action)>

A case path that can extract/embed (Int, Action) from GlobalAction.

to​Local​Environment @escaping (Global​Environment) -> Environment

A function that transforms GlobalEnvironment into Environment.

Returns

A reducer that works on GlobalState, GlobalAction, GlobalEnvironment.

for​Each(state:​action:​environment:​_:​_:​)

public func forEach<GlobalState, GlobalAction, GlobalEnvironment, ID>(state toLocalState: WritableKeyPath<GlobalState, IdentifiedArray<ID, State>>, action toLocalAction: CasePath<GlobalAction, (ID, Action)>, environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment, _ file: StaticString = #file, _ line: UInt = #line) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>

A version of pullback that transforms a reducer that works on an element into one that works on an identified array of elements.

// Global domain that holds a collection of local domains:
struct AppState { var todos: IdentifiedArrayOf<Todo> }
enum AppAction { case todo(id: Todo.ID, action: TodoAction) }
struct AppEnvironment { var mainQueue: AnySchedulerOf<DispatchQueue> }

// A reducer that works on a local domain:
let todoReducer = Reducer<Todo, TodoAction, TodoEnvironment> { ... }

// Pullback the local todo reducer so that it works on all of the app domain:
let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
  todoReducer.forEach(
    state: \.todos,
    action: /AppAction.todo(id:action:),
    environment: { _ in TodoEnvironment() }
  ),
  Reducer { state, action, environment in
    ...
  }
)

Take care when combining forEach reducers into parent domains, as order matters. Always combine forEach reducers before parent reducers that can modify the collection.

Parameters

to​Local​State Writable​Key​Path<Global​State, Identified​Array<ID, State>>

A key path that can get/set a collection of State elements inside GlobalState.

to​Local​Action Case​Path<Global​Action, (ID, Action)>

A case path that can extract/embed (Collection.Index, Action) from GlobalAction.

to​Local​Environment @escaping (Global​Environment) -> Environment

A function that transforms GlobalEnvironment into Environment.

Returns

A reducer that works on GlobalState, GlobalAction, GlobalEnvironment.

for​Each(state:​action:​environment:​_:​_:​)

public func forEach<GlobalState, GlobalAction, GlobalEnvironment, Key>(state toLocalState: WritableKeyPath<GlobalState, [Key: State]>, action toLocalAction: CasePath<GlobalAction, (Key, Action)>, environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment, _ file: StaticString = #file, _ line: UInt = #line) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>

A version of pullback that transforms a reducer that works on an element into one that works on a dictionary of element values.

Take care when combining forEach reducers into parent domains, as order matters. Always combine forEach reducers before parent reducers that can modify the dictionary.

Parameters

to​Local​State Writable​Key​Path<Global​State, [Key:​ State]>

A key path that can get/set a dictionary of State values inside GlobalState.

to​Local​Action Case​Path<Global​Action, (Key, Action)>

A case path that can extract/embed (Key, Action) from GlobalAction.

to​Local​Environment @escaping (Global​Environment) -> Environment

A function that transforms GlobalEnvironment into Environment.

Returns

A reducer that works on GlobalState, GlobalAction, GlobalEnvironment.

run(_:​_:​_:​)

public func run(_ state: inout State, _ action: Action, _ environment: Environment) -> Effect<Action, Never>

Runs the reducer.

Parameters

state inout State

Mutable state.

action Action

An action.

environment Environment

An environment.

Returns

An effect that can emit zero or more actions.

call​AsFunction(_:​_:​_:​)

public func callAsFunction(_ state: inout State, _ action: Action, _ environment: Environment) -> Effect<Action, Never>