nanocba
(Nanocba)
June 13, 2021, 2:09am
1
I have a state and action that looks like this:
struct MyState: Equatable {
var a: Int
@Indirect var b: MyState?
@Indirect var c: MyState?
}
indirect enum MyAction: Equatable {
case b(MyAction)
case c(MyAction)
case someRootAction
}
My first attempt was to implement a reducer like this:
let myReducer: Reducer<MyState, MyAction, Void> = .combine(
myReducer.optional().pullback(
state: \.b,
action: /MyAction.b,
environment: ()
),
myReducer.optional().pullback(
state: \.c,
action: /MyAction.c,
environment: ()
),
Reducer<MyState, MyAction, Void> { state, action, _ in
switch action {
case .someRootAction:
print("Root action")
return .none
}
}
)
However, that is making the library to crash at Reducer.swift line 451 even though state is not null:
return self.reducer(&state!, action, environment)
How can I model this type of recursive state/reducers?
Check out this recursive higher-order reducer from the SwiftUI Case Studies on the TCA's GitHub Page. Hope this helps!
Anachron
(Markus Kasperczyk)
June 14, 2021, 11:53am
3
Another solution could be:
protocol ReducerProtocol {
associatedtype State
associatedtype Action
associatedtype Environment
func apply(to state: inout State,
action: Action,
environment: Environment) -> Effect<Action, Never>
}
extension ReducerProtocol {
func asReducer() -> Reducer<State, Action, Environment> {...}
}
extension Optional {
func modify<T>(default defaultValue: T,
closure: (inout Wrapped) -> T) {
guard var value = self else {
return defaultValue
}
self = nil //prevent copy on write
closure(&value)
self = value
}
}
Just conform to the ReducerProtocol
, and you get a named function apply
that you can reference inside the function body:
struct MyReducer {
func apply(to state: inout MyState,
action: MyAction,
environment: ()) -> Effect<MyAction, Never> {
switch action {
case .b(let b):
state.b.modify(default: .none) {apply(to: &$0,
action: b,
environment: ()}
case .c(let c):
state.c.modify(default: .none) {apply(to: &$0,
action: c,
environment: ()}
case .someRootAction:
//whatever
}
}
}
Suggestion: you could reduce some boilerplate, if your enum looked like this:
indirect enum MyAction {
case someRootAction
case recursive(action: MyAction, keyPath: WritableKeyPath<MyState, MyState?>)
}
nanocba
(Nanocba)
June 19, 2021, 2:52pm
4
Great suggestions guys! I ended up doing it the way it's done in the case study, but I will give a try to @Anachron 's since the reducer looks cleaner in my opinion.