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 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 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 produceEffect
s, 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 Effect/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
Name | Type | Description |
---|---|---|
reducer | @escaping (inout State, Action, Environment) -> Effect<Action, Never> |
A function signature that takes state, action and environment. |
Properties
Methods
debug(_:actionFormat: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
Name | Type | Description |
---|---|---|
prefix | String |
A string with which to prefix all debug messages. |
toDebugEnvironment | @escaping (Environment) -> DebugEnvironment |
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 |
Returns
A reducer that prints debug messages for all received actions.
debugActions(_:actionFormat: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
Name | Type | Description |
---|---|---|
prefix | String |
A string with which to prefix all debug messages. |
toDebugEnvironment | @escaping (Environment) -> DebugEnvironment |
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 |
Returns
A reducer that prints debug messages for all received actions.
debug(_:state:action:actionFormat: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
Name | Type | Description |
---|---|---|
prefix | String |
A string with which to prefix all debug messages. |
toLocalState | @escaping (State) -> LocalState |
A function that filters state to be printed. |
toLocalAction | CasePath<Action, LocalAction> |
A case path that filters actions that are printed. |
toDebugEnvironment | @escaping (Environment) -> DebugEnvironment |
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 |
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
Name | Type | Description |
---|---|---|
prefix | String |
A string to print at the beginning of the formatted message for the signpost. |
log | OSLog |
An |
Returns
A reducer that has been enhanced with instrumentation.
pullback(state:action:environment:breakpointOnNil:file:line:)
@available(
*,
deprecated,
message: "'pullback' no longer takes a 'breakpointOnNil' argument"
)
public func pullback<GlobalState, GlobalAction, GlobalEnvironment>(
state toLocalState: CasePath<GlobalState, State>,
action toLocalAction: CasePath<GlobalAction, Action>,
environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment,
breakpointOnNil: Bool,
file: StaticString = #fileID,
line: UInt = #line
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>
optional(breakpointOnNil:file:line:)
@available(
*,
deprecated,
message: "'optional' no longer takes a 'breakpointOnNil' argument"
)
public func optional(
breakpointOnNil: Bool,
file: StaticString = #fileID,
line: UInt = #line
) -> Reducer<
State?, Action, Environment
>
forEach(state:action:environment:breakpointOnNil:file:line:)
@available(
*,
deprecated,
message: "'forEach' no longer takes a 'breakpointOnNil' argument"
)
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,
breakpointOnNil: Bool,
file: StaticString = #fileID,
line: UInt = #line
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>
forEach(state:action:environment:breakpointOnNil:file:line:)
@available(
*,
deprecated,
message: "'forEach' no longer takes a 'breakpointOnNil' argument"
)
public func forEach<GlobalState, GlobalAction, GlobalEnvironment, Key>(
state toLocalState: WritableKeyPath<GlobalState, [Key: State]>,
action toLocalAction: CasePath<GlobalAction, (Key, Action)>,
environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment,
breakpointOnNil: Bool,
file: StaticString = #fileID,
line: UInt = #line
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>
binding(action:)
@available(
*, deprecated,
message:
"'Reducer.binding()' no longer takes an explicit extract function and instead the reducer's 'Action' type must conform to 'BindableAction'"
)
public func binding(action toBindingAction: @escaping (Action) -> BindingAction<State>?) -> Self
binding(action:)
@available(
*, deprecated,
message:
"'Reducer.binding()' no longer takes an explicit extract function and instead the reducer's 'Action' type must conform to 'BindableAction'. Upgrade to Xcode 12.5 or greater for access to 'Reducer.binding()' and 'BindableAction'."
)
public func binding(action toBindingAction: @escaping (Action) -> BindingAction<State>?) -> Self
forEach(state:action:environment:breakpointOnNil:file:line:)
@available(*, deprecated, message: "Use the 'IdentifiedArray'-based version, instead")
public func forEach<GlobalState, GlobalAction, GlobalEnvironment>(
state toLocalState: WritableKeyPath<GlobalState, [State]>,
action toLocalAction: CasePath<GlobalAction, (Int, Action)>,
environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment,
breakpointOnNil: Bool = true,
file: StaticString = #fileID,
line: UInt = #line
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>
combine(_:)
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(file:line:)
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(state:action:environment:file:line:)-gvte
reducer. If
the parent domain modifies the child collection by moving, removing, or modifying an element
before the forEach(state:action:environment:file:line:)-gvte
reducer runs, the
forEach(state:action:environment:file:line:)-gvte
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(file:line:)
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
Name | Type | Description |
---|---|---|
reducers | Reducer |
A list of reducers. |
Returns
A single reducer.
combine(_:)
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(file:line:)
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(state:action:environment:file:line:)-gvte
reducer. If
the parent domain modifies the child collection by moving, removing, or modifying an element
before the forEach(state:action:environment:file:line:)-gvte
reducer runs, the
forEach(state:action:environment:file:line:)-gvte
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(file:line:)
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
Name | Type | Description |
---|---|---|
reducers | [Reducer] |
An array of reducers. |
Returns
A single reducer.
combined(with:)
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(file:line:)
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(state:action:environment:file:line:)-gvte
reducer. If
the parent domain modifies the child collection by moving, removing, or modifying an element
before the forEach(state:action:environment:file:line:)-gvte
reducer runs, the
forEach(state:action:environment:file:line:)-gvte
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(file:line:)
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
Name | Type | Description |
---|---|---|
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(_:)-1ern2
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
Name | Type | Description |
---|---|---|
toLocalState | WritableKeyPath<GlobalState, State> |
A key path that can get/set |
toLocalAction | CasePath<GlobalAction, Action> |
A case path that can extract/embed |
toLocalEnvironment | @escaping (GlobalEnvironment) -> Environment |
A function that transforms |
Returns
A reducer that works on GlobalState
, GlobalAction
, GlobalEnvironment
.
pullback(state:action:environment:file:line:)
public func pullback<GlobalState, GlobalAction, GlobalEnvironment>(
state toLocalState: CasePath<GlobalState, State>,
action toLocalAction: CasePath<GlobalAction, Action>,
environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment,
file: StaticString = #fileID,
line: UInt = #line
) -> 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 case path that can extract/embed a piece of local state from the global state, which is typically an enum.
-
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 overload of pullback(state:action:environment:)
differs from the other in that it
takes a CasePath
transformation for the state instead of a WritableKeyPath
. This makes it
perfect for working on enum state as opposed to struct state. In particular, you can use this
operator to pullback a reducer that operates on a single case of some state enum to work on
the entire state enum.
When used with the combine(_:)-994ak
operator you can define many reducers that work each
case of the state enum, 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:
enum AppState { case loggedIn(LoggedInState), /* rest of state */ }
enum AppAction { case loggedIn(LoggedInAction), /* other actions */ }
struct AppEnvironment { var loggedIn: LoggedInEnvironment, /* rest of dependencies */ }
// A reducer that works on the local domain:
let loggedInReducer = Reducer<LoggedInState, LoggedInAction, LoggedInEnvironment> { ... }
// Pullback the logged-in reducer so that it works on all of the app domain:
let appReducer: Reducer<AppState, AppAction, AppEnvironment> = .combine(
loggedInReducer.pullback(
state: /AppState.loggedIn,
action: /AppAction.loggedIn,
environment: { $0.loggedIn }
),
/* other reducers */
)
Take care when combining a child reducer for a particular case of enum state into its parent domain. A child reducer cannot process actions in its domain if it fails to extract its corresponding state. If a child action is sent to a reducer when its state is unavailable, it is generally considered a logic error, and a runtime warning will be logged. There are a few ways in which these errors can sneak into a code base:
-
A parent reducer sets child state to a different case when processing a child action and runs before the child reducer:
let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine( // When combining reducers, the parent reducer runs first Reducer { state, action, environment in switch action { case .child(.didDisappear): // And `nil`s out child state when processing a child action state.child = .anotherChild(AnotherChildState()) return .none ... } }, // Before the child reducer runs childReducer.pullback(state: /ParentState.child, ...) ) let childReducer = Reducer< ChildState, ChildAction, ChildEnvironment > { state, action environment in case .didDisappear: // This action is never received here because child state cannot be extracted ... }
To ensure that a child reducer can process any action that a parent may use to change its state, combine it before the parent:
let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine( // The child runs first childReducer.pullback(state: /ParentState.child, ...), // The parent runs after Reducer { state, action, environment in ... } )
-
A child effect feeds a child action back into the store when child state is unavailable:
let childReducer = Reducer< ChildState, ChildAction, ChildEnvironment > { state, action environment in switch action { case .onAppear: // An effect may want to later feed a result back to the child domain in an action return environment.apiClient .request() .map(ChildAction.response) case let .response(response): // But the child cannot process this action if its state is unavailable ... } }
It is perfectly reasonable to ignore the result of an effect when child state is
nil
, for example one-off effects that you don't want to cancel. However, many long-living effects should be explicitly canceled when tearing down a child domain:let childReducer = Reducer< ChildState, ChildAction, ChildEnvironment > { state, action environment in struct MotionId: Hashable {} switch action { case .onAppear: // Mark long-living effects that shouldn't outlive their domain cancellable return environment.motionClient .start() .map(ChildAction.motion) .cancellable(id: MotionId()) case .onDisappear: // And explicitly cancel them when the domain is torn down return .cancel(id: MotionId()) ... } }
-
A view store sends a child action when child state is
nil
:WithViewStore(self.parentStore) { parentViewStore in // If child state is `nil`, it cannot process this action. Button("Child Action") { parentViewStore.send(.child(.action)) } ... }
Use
Store/scope(state:action:)
withSwitchStore
to ensure that views can only send child actions when the child domain is available.SwitchStore(self.parentStore) { CaseLet(state: /ParentState.child, action: ParentAction.child) { childStore in // This destination only appears when child state matches WithViewStore(childStore) { childViewStore in // So this action can only be sent when child state is available Button("Child Action") { childViewStore.send(.action) } } } ... }
Parameters
Name | Type | Description |
---|---|---|
toLocalState | CasePath<GlobalState, State> |
A case path that can extract/embed |
toLocalAction | CasePath<GlobalAction, Action> |
A case path that can extract/embed |
toLocalEnvironment | @escaping (GlobalEnvironment) -> Environment |
A function that transforms |
Returns
A reducer that works on GlobalState
, GlobalAction
, GlobalEnvironment
.
optional(file:line:)
public func optional(
file: StaticString = #fileID,
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(state:action:environment:)
to transform a reducer on a
non-optional child domain into a reducer that can be combined with a reducer on a parent
domain that contains some optional child 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. An optional reducer cannot
process actions in its domain when its state is nil
. If a child action is sent to an
optional reducer when child state is nil
, it is generally considered a logic error. There
are a few ways in which these errors can sneak into a code base:
-
A parent reducer sets child state to
nil
when processing a child action and runs before the child reducer:let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine( // When combining reducers, the parent reducer runs first Reducer { state, action, environment in switch action { case .child(.didDisappear): // And `nil`s out child state when processing a child action state.child = nil return .none ... } }, // Before the child reducer runs childReducer.optional().pullback(...) ) let childReducer = Reducer< ChildState, ChildAction, ChildEnvironment > { state, action environment in case .didDisappear: // This action is never received here because child state is `nil` in the parent ... }
To ensure that a child reducer can process any action that a parent may use to
nil
out its state, combine it before the parent:let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine( // The child runs first childReducer.optional().pullback(...), // The parent runs after Reducer { state, action, environment in ... } )
-
A child effect feeds a child action back into the store when child state is
nil
:let childReducer = Reducer< ChildState, ChildAction, ChildEnvironment > { state, action environment in switch action { case .onAppear: // An effect may want to feed its result back to the child domain in an action return environment.apiClient .request() .map(ChildAction.response) case let .response(response): // But the child cannot process this action if its state is `nil` in the parent ... } }
It is perfectly reasonable to ignore the result of an effect when child state is
nil
, for example one-off effects that you don't want to cancel. However, many long-living effects should be explicitly canceled when tearing down a child domain:let childReducer = Reducer< ChildState, ChildAction, ChildEnvironment > { state, action environment in struct MotionId: Hashable {} switch action { case .onAppear: // Mark long-living effects that shouldn't outlive their domain cancellable return environment.motionClient .start() .map(ChildAction.motion) .cancellable(id: MotionId()) case .onDisappear: // And explicitly cancel them when the domain is torn down return .cancel(id: MotionId()) ... } }
-
A view store sends a child action when child state is
nil
:WithViewStore(self.parentStore) { parentViewStore in // If child state is `nil`, it cannot process this action. Button("Child Action") { parentViewStore.send(.child(.action)) } ... }
Use
Store/scope(state:action:)
withIfLetStore
orStore/ifLet(then:else:)
to ensure that views can only send child actions when the child domain is non-nil
.IfLetStore( self.parentStore.scope(state: { $0.child }, action: { .child($0) } ) { childStore in // This destination only appears when child state is non-`nil` WithViewStore(childStore) { childViewStore in // So this action can only be sent when child state is non-`nil` Button("Child Action") { childViewStore.send(.action) } } ... }
Returns
A reducer that works on optional state.
forEach(state:action:environment:file:line:)
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 = #fileID,
line: UInt = #line
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>
A version of pullback(state:action:environment:)
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(state:action:environment:file:line:)-gvte
reducers into
parent domains, as order matters. Always combine
forEach(state:action:environment:file:line:)-gvte
reducers before parent reducers that
can modify the collection.
Parameters
Name | Type | Description |
---|---|---|
toLocalState | WritableKeyPath<GlobalState, IdentifiedArray<ID, State>> |
A key path that can get/set a collection of |
toLocalAction | CasePath<GlobalAction, (ID, Action)> |
A case path that can extract/embed |
toLocalEnvironment | @escaping (GlobalEnvironment) -> Environment |
A function that transforms |
Returns
A reducer that works on GlobalState
, GlobalAction
, GlobalEnvironment
.
forEach(state:action:environment:file:line:)
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 = #fileID,
line: UInt = #line
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>
A version of pullback(state:action:environment:)
that transforms a reducer that works on
an element into one that works on a dictionary of element values.
Take care when combining forEach(state:action:environment:file:line:)-21wow
reducers into
parent domains, as order matters. Always combine
forEach(state:action:environment:file:line:)-21wow
reducers before parent reducers that
can modify the dictionary.
Parameters
Name | Type | Description |
---|---|---|
toLocalState | WritableKeyPath<GlobalState, [Key: State]> |
A key path that can get/set a dictionary of |
toLocalAction | CasePath<GlobalAction, (Key, Action)> |
A case path that can extract/embed |
toLocalEnvironment | @escaping (GlobalEnvironment) -> Environment |
A function that transforms |
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
Name | Type | Description |
---|---|---|
state | inout State |
Mutable state. |
action | Action |
An action. |
environment | Environment |
An environment. |
Returns
An effect that can emit zero or more actions.
callAsFunction(_:_:_:)
public func callAsFunction(
_ state: inout State,
_ action: Action,
_ environment: Environment
) -> Effect<Action, Never>