TCA: How to combine two or more reducers with the LoadAndNavigate approach

Hi there! :wave:

First of all, I tried to search for answers and examples concerning what I'm about to ask. However I couldn't find anything that could help me.

Following the examples:

I've been trying to have a list of two or more NavigationLink views, without using ForEach(viewStore.rows) { row in ... }, and having two or more reducers, so that can use a different Environment in each view.

For example, considering 03-Navigation-LoadThenNavigate, the body would look like as follows:

var body: some View {
        WithViewStore(self.store) { viewStore in
            Form {
                Section(header: Text(readMe)) {
                    NavigationLink(
                        destination: IfLetStore(
                            self.store.scope(
                                state: { $0.optionalCounter }, action: LoadThenNavigateAction.optionalCounter),
                            then: CounterView.init(store:)
                        ),
                        isActive: viewStore.binding(
                            get: { $0.isNavigationActive },
                            send: LoadThenNavigateAction.setNavigation(isActive:)
                        )
                    ) {
                        HStack {
                            Text("Load optional counter")
                            if viewStore.isActivityIndicatorVisible {
                                Spacer()
                                ActivityIndicator()
                            }
                        }
                    }
                    NavigationLink(
                        destination: IfLetStore(
                            self.store.scope(
                                state: { $0.optionalCounter }, action: LoadThenNavigateAction.optionalCounter),
                            then: CounterView.init(store:)
                        ),
                        isActive: viewStore.binding(
                            get: { $0.isNavigationActive },
                            send: LoadThenNavigateAction.setNavigation(isActive:)
                        )
                    ) {
                        HStack {
                            Text("Load optional counter")
                            if viewStore.isActivityIndicatorVisible {
                                Spacer()
                                ActivityIndicator()
                            }
                        }
                    }
                }
            }
        }
        .navigationBarTitle("Load then navigate")
    }
}

How would you then define the loadThenNavigateReducer so that you can easily have different Environment ? one for "production" purposes and one for mock/demo purposes, for example ?

Is this actually the right approach to use? if not, do you have any other solution for this problem ?

Hi there :wave:

I should also add hat the appReducer needs to take care somehow of which "sub-reducer" to use

for example, assuming that there are two CounterViews (as defined here), then:

let navigateAndLoadReducer = .combine(
    counterReducer1
        .optional()
        .pullback(
            state: \.optionalCounter,
            action: /NavigateAndLoadAction.optionalCounter,
            environment: { _ in CounterEnvironment1() }
        ),
    counterReducer2
        .optional()
        .pullback(
            state: \.optionalCounter,
            action: /NavigateAndLoadAction.optionalCounter,
            environment: { _ in CounterEnvironment2() }
        ),
    Reducer { state, action, environment in
        switch action {
        case .setNavigation(isActive: true):
            state.isNavigationActive = true
            return Effect(value: .setNavigationIsActiveDelayCompleted)
                .delay(for: 1, scheduler: environment.mainQueue)
                .eraseToEffect()
            
        case .setNavigation(isActive: false):
            state.isNavigationActive = false
            state.optionalCounter = nil
            return .none
            
        case .setNavigationIsActiveDelayCompleted:
            state.optionalCounter = CounterState()
            return .none
            
        case .optionalCounter:
            return .none
        }
    }
)

Tapping on one cell will trigger both.

At the moment I won't use the LoadAndNavigate approach. I'll use .onAppear/.onDisapper one. Check the RootView of TCA Examples