Hello! Loving TCA so far. I'm currently using TCA in a side project at the company I work for, and it's been going really well. Hoping to convince others to get on board. One wall I'm hitting right now though is that I'm trying to use this recursive pattern as seen in the case studies:
04-HigherOrderReducers-Recursion.swift
extension Reducer {
static func recurse(
_ reducer: @escaping (Reducer, inout State, Action, Environment) -> Effect<Action, Never>
) -> Reducer {
var `self`: Reducer!
self = Reducer { state, action, environment in
reducer(self, &state, action, environment)
}
return self
}
}
That seems to work for basic actions, but as soon as I put an action in an Effect, it gets called on the "original" reducer. I've modified the case study to demonstrate. You can see below that I've added two actions to the nestedReducer
:
let nestedReducer = Reducer<
NestedState, NestedAction, NestedEnvironment
>.recurse { `self`, state, action, environment in
switch action {
case .onAppear:
return Effect(value: NestedAction.anAction)
case .anAction:
return .none
And onAppear
is called in the NestedView
:
struct NestedView: View {
let store: Store<NestedState, NestedAction>
var body: some View {
WithViewStore(self.store.scope(state: { $0.description })) { viewStore in
Form {
Section(header: Text(template: readMe, .caption)) {
ForEachStore(
self.store.scope(state: { $0.children }, action: NestedAction.node(index:action:))
) { childStore in
WithViewStore(childStore) { childViewStore in
HStack {
TextField(
"Untitled",
text: childViewStore.binding(get: { $0.description }, send: NestedAction.rename)
)
Spacer()
NavigationLink(
destination: NestedView(store: childStore)
) {
Text("")
}
}
}
}
.onDelete { viewStore.send(.remove($0)) }
}
}
.navigationBarTitle(viewStore.state.isEmpty ? "Untitled" : viewStore.state)
.navigationBarItems(
trailing: Button("Add row") { viewStore.send(.append) }
)
}.onAppear {
ViewStore(self.store).send(.onAppear)
}
}
}
onAppear
calls anAction
through an Effect. If you run the case study, drill down to a child, and set a breakpoint at onAppear
you'll see that the state is the expected state (the state of the child reducer). However, if you point a breakpoint at anAction
, the state is not the state of the child. It is the root state. In fact, if you run the app and recursively add rows and navigate deeper and deeper, the breakpoint on anAction
will always show you the root state. To me, that's unexpected. I would expect the action in the effect to come through the same reducer.
My use case for this is that I will be making an API call and displaying a list of children. Tapping on a child will load another instance of the same view, which has its own children and its own API call. This will keep going recursively. When I make an API call, I need to map it to an action through an Effect, which is where I'm having an issue. It's not calling back to the reducer that I'm expecting.
Am I missing something? Is there a different pattern that can be used in situations like this? Thanks in advance.