Send an action from one reducer to another

Hello, I want to achieve an effect similar to this:

So, I have a workout state, which holds var sets: IdentifiedArrayOf<ActiveExerciseRowState> = [] and shows the exercises as follows:

      ForEachStore(
        self.store.scope(state: \.sets, action: ActiveWorkoutAction.exerciseSet(id:action:)),
        content: ActiveExerciseRowView.init(store:)
      )

with the following reducer:

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?

Hey @smeshko!

@mbrandonw actually sketched out a resending higher-order reducer in this thread: Responding to `FooClient.Action` regardless of which view generates `Effect`s emitting them

You could maybe tack that onto activeWorkoutReducer:

let activeWorkoutReducer = Reducer.combine(
  ...
)
.resending(..., to: ...)

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)) }
  )
Terms of Service

Privacy Policy

Cookie Policy