@@ -55,17 +55,99 @@ public struct Reducer<State, Action, Environment> {
55
55
Self { _, _, _ in . none }
56
56
}
57
57
58
- /// Combines many reducers into a single one by running each one on the state, and merging
58
+ /// Combines many reducers into a single one by running each one on state in order , and merging
59
59
/// all of the effects.
60
60
///
61
+ /// It is important to note that the order of combining reducers matter. Combining `reducerA` with
62
+ /// `reducerB` is not necessarily the same as combining `reducerB` with `reducerA`.
63
+ ///
64
+ /// This can become an issue when working with reducers that have overlapping domains. For
65
+ /// example, if `reducerA` embeds the domain of `reducerB` and reacts to its actions or modifies
66
+ /// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state
67
+ /// _before_ or _after_ `reducerB` runs.
68
+ ///
69
+ /// This is perhaps most easily seen when working with `optional` reducers, where the parent
70
+ /// domain may listen to the child domain and `nil` out its state. If the parent reducer runs
71
+ /// before the child reducer, then the child reducer will not be able to react to its own action.
72
+ ///
73
+ /// Similar can be said for a `forEach` reducer. If the parent domain modifies the child
74
+ /// collection by moving, removing, or modifying an element before the `forEach` reducer runs, the
75
+ /// `forEach` reducer may perform its action against the wrong element, an element that no longer
76
+ /// exists, or an element in an unexpected state.
77
+ ///
78
+ /// Running a parent reducer before a child reducer can be considered an application logic
79
+ /// error, and can produce assertion failures. So you should almost always combine reducers in
80
+ /// order from child to parent domain.
81
+ ///
82
+ /// Here is an example of how you should combine an `optional` reducer with a parent domain:
83
+ ///
84
+ /// let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(
85
+ /// // Combined before parent so that it can react to `.dismiss` while state is non-`nil`.
86
+ /// childReducer.optional.pullback(
87
+ /// state: \.child,
88
+ /// action: /ParentAction.child,
89
+ /// environment: { $0.child }
90
+ /// ),
91
+ /// // Combined after child so that it can `nil` out child state upon `.child(.dismiss)`.
92
+ /// Reducer { state, action, environment in
93
+ /// switch action
94
+ /// case .child(.dismiss):
95
+ /// state.child = nil
96
+ /// return .none
97
+ /// ...
98
+ /// }
99
+ /// },
100
+ /// )
101
+ ///
61
102
/// - Parameter reducers: A list of reducers.
62
103
/// - Returns: A single reducer.
63
104
public static func combine( _ reducers: Reducer ... ) -> Reducer {
64
105
. combine( reducers)
65
106
}
66
107
67
- /// Combines an array of reducers into a single one by running each one on the state, and
68
- /// merging all of the effects.
108
+ /// Combines many reducers into a single one by running each one on state in order, and merging
109
+ /// all of the effects.
110
+ ///
111
+ /// It is important to note that the order of combining reducers matter. Combining `reducerA` with
112
+ /// `reducerB` is not necessarily the same as combining `reducerB` with `reducerA`.
113
+ ///
114
+ /// This can become an issue when working with reducers that have overlapping domains. For
115
+ /// example, if `reducerA` embeds the domain of `reducerB` and reacts to its actions or modifies
116
+ /// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state
117
+ /// _before_ or _after_ `reducerB` runs.
118
+ ///
119
+ /// This is perhaps most easily seen when working with `optional` reducers, where the parent
120
+ /// domain may listen to the child domain and `nil` out its state. If the parent reducer runs
121
+ /// before the child reducer, then the child reducer will not be able to react to its own action.
122
+ ///
123
+ /// Similar can be said for a `forEach` reducer. If the parent domain modifies the child
124
+ /// collection by moving, removing, or modifying an element before the `forEach` reducer runs, the
125
+ /// `forEach` reducer may perform its action against the wrong element, an element that no longer
126
+ /// exists, or an element in an unexpected state.
127
+ ///
128
+ /// Running a parent reducer before a child reducer can be considered an application logic
129
+ /// error, and can produce assertion failures. So you should almost always combine reducers in
130
+ /// order from child to parent domain.
131
+ ///
132
+ /// Here is an example of how you should combine an `optional` reducer with a parent domain:
133
+ ///
134
+ /// let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine(
135
+ /// // Combined before parent so that it can react to `.dismiss` while state is non-`nil`.
136
+ /// childReducer.optional.pullback(
137
+ /// state: \.child,
138
+ /// action: /ParentAction.child,
139
+ /// environment: { $0.child }
140
+ /// ),
141
+ /// // Combined after child so that it can `nil` out child state upon `.child(.dismiss)`.
142
+ /// Reducer { state, action, environment in
143
+ /// switch action
144
+ /// case .child(.dismiss):
145
+ /// state.child = nil
146
+ /// return .none
147
+ /// ...
148
+ /// }
149
+ /// },
150
+ /// )
69
151
///
70
152
/// - Parameter reducers: An array of reducers.
71
153
/// - Returns: A single reducer.
@@ -75,8 +157,52 @@ public struct Reducer<State, Action, Environment> {
75
157
}
76
158
}
77
159
78
- /// Combines the current reducer with another given reducer by running each one on the state,
79
- /// and merging their effects.
160
+ /// Combines many reducers into a single one by running each one on state in order, and merging
161
+ /// all of the effects.
162
+ ///
163
+ /// It is important to note that the order of combining reducers matter. Combining `reducerA` with
164
+ /// `reducerB` is not necessarily the same as combining `reducerB` with `reducerA`.
165
+ ///
166
+ /// This can become an issue when working with reducers that have overlapping domains. For
167
+ /// example, if `reducerA` embeds the domain of `reducerB` and reacts to its actions or modifies
168
+ /// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state
169
+ /// _before_ or _after_ `reducerB` runs.
170
+ ///
171
+ /// This is perhaps most easily seen when working with `optional` reducers, where the parent
172
+ /// domain may listen to the child domain and `nil` out its state. If the parent reducer runs
173
+ /// before the child reducer, then the child reducer will not be able to react to its own action.
174
+ ///
175
+ /// Similar can be said for a `forEach` reducer. If the parent domain modifies the child
176
+ /// collection by moving, removing, or modifying an element before the `forEach` reducer runs, the
177
+ /// `forEach` reducer may perform its action against the wrong element, an element that no longer
178
+ /// exists, or an element in an unexpected state.
179
+ ///
180
+ /// Running a parent reducer before a child reducer can be considered an application logic
181
+ /// error, and can produce assertion failures. So you should almost always combine reducers in
182
+ /// order from child to parent domain.
183
+ ///
184
+ /// Here is an example of how you should combine an `optional` reducer with a parent domain:
185
+ ///
186
+ /// let parentReducer: Reducer<ParentState, ParentAction, ParentEnvironment> =
187
+ /// // Run before parent so that it can react to `.dismiss` while state is non-`nil`.
188
+ /// childReducer
189
+ /// .optional
190
+ /// .pullback(
191
+ /// state: \.child,
192
+ /// action: /ParentAction.child,
193
+ /// environment: { $0.child }
194
+ /// )
195
+ /// // Combined after child so that it can `nil` out child state upon `.child(.dismiss)`.
196
+ /// .combined(
197
+ /// with: Reducer { state, action, environment in
198
+ /// switch action
199
+ /// case .child(.dismiss):
200
+ /// state.child = nil
201
+ /// return .none
202
+ /// ...
203
+ /// }
204
+ /// }
205
+ /// )
80
206
///
81
207
/// - Parameter other: Another reducer.
82
208
/// - Returns: A single reducer.
@@ -140,7 +266,8 @@ public struct Reducer<State, Action, Environment> {
140
266
/// only running the non-optional reducer when state is non-nil.
141
267
///
142
268
/// Often used in tandem with `pullback` to transform a reducer on a non-optional local domain
143
- /// into a reducer on a global domain that contains an optional local domain:
269
+ /// into a reducer that can be combined with a reducer on a global domain that contains some
270
+ /// optional local domain:
144
271
///
145
272
/// // Global domain that holds an optional local domain:
146
273
/// struct AppState { var modal: ModalState? }
@@ -151,12 +278,20 @@ public struct Reducer<State, Action, Environment> {
151
278
/// let modalReducer = Reducer<ModalState, ModalAction, ModalEnvironment { ... }
152
279
///
153
280
/// // Pullback the local modal reducer so that it works on all of the app domain:
154
- /// let appReducer: Reducer<AppState, AppAction, AppEnvironment> =
281
+ /// let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
155
282
/// modalReducer.optional().pullback(
156
283
/// state: \.modal,
157
284
/// action: /AppAction.modal,
158
285
/// environment: { ModalEnvironment(mainQueue: $0.mainQueue) }
159
- /// )
286
+ /// ),
287
+ /// Reducer { state, action, environment in
288
+ /// ...
289
+ /// }
290
+ /// )
291
+ ///
292
+ /// Take care when combining optional reducers into parent domains, as order matters. Always
293
+ /// combine optional reducers _before_ parent reducers that can `nil` out the associated optional
294
+ /// state.
160
295
///
161
296
/// - See also: `IfLetStore`, a SwiftUI helper for transforming a store on optional state into a
162
297
/// store on non-optional state.
@@ -205,12 +340,19 @@ public struct Reducer<State, Action, Environment> {
205
340
/// let todoReducer = Reducer<Todo, TodoAction, TodoEnvironment> { ... }
206
341
///
207
342
/// // Pullback the local todo reducer so that it works on all of the app domain:
208
- /// let appReducer: Reducer<AppState, AppAction, AppEnvironment> =
343
+ /// let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
209
344
/// todoReducer.forEach(
210
345
/// state: \.todos,
211
346
/// action: /AppAction.todo(index:action:),
212
347
/// environment: { _ in TodoEnvironment() }
213
- /// )
348
+ /// ),
349
+ /// Reducer { state, action, environment in
350
+ /// ...
351
+ /// }
352
+ /// )
353
+ ///
354
+ /// Take care when combining `forEach` reducers into parent domains, as order matters. Always
355
+ /// combine `forEach` reducers _before_ parent reducers that can modify the collection.
214
356
///
215
357
/// - Parameters:
216
358
/// - toLocalState: A key path that can get/set an array of `State` elements inside.
@@ -276,12 +418,19 @@ public struct Reducer<State, Action, Environment> {
276
418
/// let todoReducer = Reducer<Todo, TodoAction, TodoEnvironment> { ... }
277
419
///
278
420
/// // Pullback the local todo reducer so that it works on all of the app domain:
279
- /// let appReducer: Reducer<AppState, AppAction, AppEnvironment> =
421
+ /// let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
280
422
/// todoReducer.forEach(
281
423
/// state: \.todos,
282
424
/// action: /AppAction.todo(id:action:),
283
425
/// environment: { _ in TodoEnvironment() }
284
- /// )
426
+ /// ),
427
+ /// Reducer { state, action, environment in
428
+ /// ...
429
+ /// }
430
+ /// )
431
+ ///
432
+ /// Take care when combining `forEach` reducers into parent domains, as order matters. Always
433
+ /// combine `forEach` reducers _before_ parent reducers that can modify the collection.
285
434
///
286
435
/// - Parameters:
287
436
/// - toLocalState: A key path that can get/set a collection of `State` elements inside
@@ -342,6 +491,9 @@ public struct Reducer<State, Action, Environment> {
342
491
/// A version of `pullback` that transforms a reducer that works on an element into one that works
343
492
/// on a dictionary of element values.
344
493
///
494
+ /// Take care when combining `forEach` reducers into parent domains, as order matters. Always
495
+ /// combine `forEach` reducers _before_ parent reducers that can modify the dictionary.
496
+ ///
345
497
/// - Parameters:
346
498
/// - toLocalState: A key path that can get/set a dictionary of `State` values inside
347
499
/// `GlobalState`.
0 commit comments