What is the best practice for changing a child state in NavigationStack? #3208
-
First of all, thank you for the amazing work. If I am using NavigationStack, A -> B -> C, and A is the reducer holding the NavigationStack path, how can I change the state of B in A when C changes some states and B needs to respond? @Reducer
struct ÅFeature {
struct State {
var path: [Path] = []
}
...
enum Path {
case a(AFeature)
case b(BFeature)
case c(CFeature)
}
enum Action {
...
case path(StackActionOf<Path>)
}
var body: some ReducerOf<Self> {
Reduce<State, Action> { state, action in
switch action {
case .path(.element(id: let cPathId, action: .c(.didChange(let cState)))):
// What should I do?
...
return .none
case ...
}
}
}
First, I tried directly modifying the properties in bState, but if I understand correctly, due to the copy-on-write (COW) nature, I cannot directly modify the properties of B’s state in the navigationStack and make it effective because I only obtained a copy of bState. switch action {
case .path(.element(id: let cPathId, action: .c(.didChange(let cState)))):
// 1
guard let bStackId = state.path.ids.first(where: { stackElementID in
state.path[id: stackElementID]?.b != nil
}),
let bState = state.path[id: bStackId]?.b else {
debugLog("can not find personalityCenterStackId")
return .none
}
bState.property = ....
return .none
I carefully read through this discussion, and based on mbrandonw’s response, I tried mapping the path action in A to return an action from B’s reducer, I don’t know if I’m missing something, but it seems doesn't work. @Reducer
struct BFeature {
struct State {
...
mutating func update(…) -> Effect<BFeature.Action> {
...
}
}
}
switch action {
case .path(.element(id: let cPathId, action: .c(.didChange(let cState)))):
// 1
guard let bStackId = state.path.ids.first(where: { stackElementID in
state.path[id: stackElementID]?.b != nil
}),
let bState = state.path[id: bStackId]?.b else {
debugLog("can not find personalityCenterStackId")
return .none
}
return bState
.update(...)
.map { Action.path(.element(id: bStackId, action: .b($0)))}
Currently, I discovered swift-case-paths, which seems to have methods for directly modifying enum associated values, and it works. @Reducer
struct BFeature {
struct State {
...
mutating func update(…) {
...
}
}
}
switch action {
case .path(.element(id: let cPathId, action: .c(.didChange(let cState)))):
guard let bStackId = state.path.ids.first(where: { stackElementID in
state.path[id: stackElementID]?.b != nil
}) else {
debugLog("can not find personalityCenterStackId")
return .none
}
state.path[id: bStackId]?.modify(\.b, yield: { bState in
bState.update(cState: cState)
})
return .none
But it feels a bit strange, and I’m not sure if this is the best practice. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 5 replies
-
Hi @MorningStarJ, your approaches are basically correct, though maybe a little verbose. Here is another approach: guard let bIndex = state.path.firstIndex(where: { $0.is(\.b) })
else {
return
}
state.path[bIndex].modify(\.b) {
// Mutate $0
} There |
Beta Was this translation helpful? Give feedback.
Hi @MorningStarJ, your approaches are basically correct, though maybe a little verbose. Here is another approach:
There
is(casePath)
function allows you to easily check if an enum matches a case, and it comes from our CasePaths library. And you can fetch the positional index rather than ID so that you can subscript directly intopath
. And then you can usemodify
with a case path to mutate the data inside the path.