Pulling back Effects to multiple global states from single reducer

Hi!
I have three pullbacks in my global reducer that pulls and maps to three different global states properties from a single reducer function. Everything works as expected (like the two counters example) except that I have some effects that’s only “catched” by one of the pullbacks.
The reducer function have an “ELM-like” subscription that receives keyboard notifications from the environment and maps it to the reducers LocalAction.keyboardHeightChanged.


private let globalReducer = Reducer<GlobalState, GlobalAction, SystemEnvironment<GlobalEnvironment>>.combine(

localReducer.pullback(
  state: \GlobalState.state1,
  action: /GlobalAction.state1,
  environment: { _ in .live(environment: LocalEnvironment()) }
),
localReducer.pullback(
  state: \GlobalState.state2,
  action: /GlobalAction.state2,
  environment: { _ in .live(environment: LocalEnvironment()) }
),
...
).debug()

let localReducer = Reducer<LocalState, LocalAction, SystemEnvironment<LocalEnvironment>>.combine(
Reducer { state, action, environment in
switch action {¨

...

case let .keyboardHeightChanged(height):
  state.keyboardHeight = height
  return .none
}
},
.subscriptions { state, environment in
  return [
  KeyboardManagerID() :
  environment.keyboardmanager.updates(KeyboardManagerID())
  .receive(on: environment.mainQueue)
  .eraseToEffect()
  .map(LocalAction.keyboardHeightChanged)
  ]
})

What’s the general approach to these kind of problems?

I'm not a pro in composable-architecture yet, but I like your approach. The only thing to mention is that I'd make your local prefix for reducer, state & action - more concrete keyboardObserver prefix, for example, to only handle keyboard events and isolate the logic. There are should be only those cases in reducer, which you want to handle from the keyboard. And I'd also create state1Reducer & state2Reducer for local state management, which wrap keyboardObserverReducer.

So the final tree of reducers would look like:

globalReducer                               // reducer for globalState
    - localState1Reducer.pullback           // reducer for \.GlobalState.state1
        - keyboardObserverReducer.pullback  // reducer for \.GlobalState.state1\.State1.keyboard
            * switch keyboardObserverState
        * switch state1
    - localState2Reducer.pullback           // reducer for \.GlobalState.state2
        - keyboardObserverReducer.pullback  // reducer for \.State1.keyboard
            * switch keyboardObserverState
        * switch state2
    * switch globalState

Yes, that was something like the solution I went for in the end to get it working;
Combining three local reducers with a subscription each and pulling them in instead of pulling three instances of a local reducer combined with a subscription.
Makes more sense in code:


private let localReducers = Reducer<GlobalState, GlobalAction, SystemEnvironment<GlobalEnvironment>>.combine(
    localReducer
    .pullback(
        state: \GlobalState.local1,
        action: /GlobalAction.local1,
        environment: { _ in .live(environment: LocalEnvironment()) }
    ),
    localReducer.combined(
        with: .subscriptions { state, environment in
            return [
                KeyboardManagerID() :
                environment.keyboardmanager.updates(KeyboardManagerID())
                .receive(on: environment.mainQueue)
                .eraseToEffect()
                .map(LocalAction.keyboardHeightChanged)
            ]
        }
    )
    .pullback(
        state: \GlobalState.local2,
        action: /GlobalAction.local2,
        environment: { _ in .live(environment: LocalEnvironment()) }
    ),
    localReducer.combined(
        with: .subscriptions { state, environment in
            return [
                KeyboardManagerID() :
                environment.keyboardmanager.updates(KeyboardManagerID())
                .receive(on: environment.mainQueue)
                .eraseToEffect()
                .map(LocalAction.keyboardHeightChanged)
            ]
        }
    )
    .pullback(
        state: \GlobalState.local3,
        action: /GlobalAction.local3,
        environment: { _ in .live(environment: LocalEnvironment()) }
    )
)