public let activeWorkoutReducer = Reducer<ActiveWorkoutState, ActiveWorkoutAction, ActiveWorkoutEnvironment>.combine(
Reducer { state, action, environment in
switch action {
case .exerciseSet(let id, let rowAction):
switch rowAction {
case .exerciseFinished:
state.moveToNextExercise()
default: break
}
}
return .none
},
activeExerciseRowReducer.forEach(
state: \.sets,
action: /ActiveWorkoutAction.exerciseSet(id:action:),
environment: { _ in ActiveExerciseRowEnvironment(mainQueue: DispatchQueue.main.eraseToAnyScheduler()) } )
)
The exercise has an action exerciseBegin with the following implementation:
case .exerciseBegin:
state.secondsLeft = state.set.duration
return Effect
.timer(id: TimerId(), every: 1, tolerance: .zero, on: environment.mainQueue)
.map { _ in ActiveExerciseRowAction.timerTicked }
Since the workout knows which exercise should be next, how can I send the exerciseBegin from the workout reducer to the corresponding exercise reducer?
Hello @stephencelis and thanks for the quick response! Could you maybe elaborate a bit more on the solution? I am not sure how to use the resending with a variadic number of elements and having 2 different types of Actions in the case and to. Thanks!
Can you post a lil more code so I can see the action definition, etc.? The from and to don't have to match exactly, but you will need to have all the information you need in the from case to construct a to case.
An abstract example of forwarding a String action to a Bool action:
enum ChildActionA { case foo(String) }
enum ChildActionB { case bar(Bool) }
enum ParentAction { case childA(ChildActionA), childB(ChildActionB) }
...
let reducer = Reducer<Void, ParentAction, Void>.empty
// types very explicit:
.resending(
from: { (string: String) -> ParentAction in
ParentAction.childA(ChildActionA.foo(string))
},
to: { (string: String) -> ParentAction in
ParentAction.childB(ChildActionB.bar(string.count > 5))
}
)
// or, with abbreviated types:
.resending(
from: { .childA(.foo($0)) }, to: { .childB(.bar($0.count > 5)) }
)
It's definitely not deprecated, it just doesn't come with TCA. It might be useful enough to include some day, but it's simple enough that we recommend folks copy and paste it into their projects to use it
As Brandon's resending implementation uses the now deprecated CasePath.case(case), I thought I'd give it a shot and reimplement it using CasePaths.
extension Reducer {
func resending<Value>(
_ extract: @escaping (Action) -> Value?,
to embed: @escaping (Value) -> Action
) -> Self {
.combine(
self,
.init { _, action, _ in
if let value = extract(action) {
return Effect(value: embed(value))
} else {
return .none
}
}
)
}
func resending<Value>(
_ `case`: CasePath<Action, Value>,
to other: CasePath<Action, Value>
) -> Self {
resending(`case`.extract(from:), to: other.embed(_:))
}
func resending<Value>(
_ `case`: CasePath<Action, Value>,
to other: @escaping (Value) -> Action
) -> Self {
resending(`case`.extract(from:), to: other)
}
func resending<Value>(
_ extract: @escaping (Action) -> Value?,
to other: CasePath<Action, Value>
) -> Self {
resending(extract, to: other.embed(_:))
}
}
Usage
parentReducer
.resending(/ParentAction.fooClient, to: /ParentAction.child1 .. Child1Action.fooClient)
.resending(/ParentAction.fooClient, to: /ParentAction.child2 .. Child2Action.fooClient)
.resending(/ParentAction.fooClient, to: { value in .boolAction(value > 5) })
If extensions like this one don't have 'a place' in the core lib, should we (as a community) maybe create a space similar to CombineExt and RxSwiftExt that is managed by a TCACommunity?
This is a more convient way to do it(today I have reducers just for that).
BTW is there a more compact way to write this ?
.resending(
{ action in
if case let .local(.fetchFromCloud(recordKey)) = action { return recordKey }
return nil
}, to: { .core(.fetchFromCloud($0)) }
)