Leave view updating parent view and messing with navigation #1853
-
Hi everyone, I am trying to develop a new application using TCA and I am currently loving it! However, I am encountering a behaviour that I don't really know how to fix. I wrote a little project to recreate the problem so that anyone that has encountered something similar can help me to overcome this. In the code below I have three screens: As I tested, removing the Any kind of help will be so much appreciated as I am starting to mess with this awesome library. import SwiftUI
import ComposableArchitecture
@main
struct BlankProjectApp: App {
var body: some Scene {
WindowGroup {
RootView(
store: Store(
initialState: Root.State(),
reducer: Root()
)
)
}
}
}
// MARK: - Counter
struct Counter: ReducerProtocol {
struct State: Equatable {
var count: Int = 0
}
enum Action {
case increment, decrement
}
func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .increment:
state.count += 1
return .none
case .decrement:
state.count -= 1
return .none
}
}
}
struct CounterView: View {
var store: StoreOf<Counter>
var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
VStack {
Text(String(viewStore.state.count))
HStack {
Button {
viewStore.send(.decrement)
} label: {
Text("-")
}
Button {
viewStore.send(.increment)
} label: {
Text("+")
}
}
}
}
}
}
// MARK: - Intermediate screen
struct Intermediate: ReducerProtocol {
struct State: Equatable {
var counter = Counter.State()
}
enum Action {
case counter(Counter.Action)
}
var body: some ReducerProtocol<State, Action> {
Scope(state: \.counter, action: /Action.counter) {
Counter()
}
}
}
struct IntermediateView: View {
var store: StoreOf<Intermediate>
var body: some View {
Form {
NavigationLink {
CounterView(store: store.scope(state: \.counter, action: Intermediate.Action.counter))
} label: {
Text("Counter")
}
}
}
}
// MARK: - Root
struct Root: ReducerProtocol {
struct State: Equatable {
var intermediate = Intermediate.State()
}
enum Action {
case intermediate(Intermediate.Action)
}
var body: some ReducerProtocol<State, Action> {
Scope(state: \.intermediate, action: /Action.intermediate) {
Intermediate()
}
}
}
struct RootView: View {
var store: StoreOf<Root>
var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
NavigationView {
Form {
Text(String(viewStore.intermediate.counter.count))
NavigationLink {
IntermediateView(store: store.scope(state: \.intermediate, action: Root.Action.intermediate))
} label: {
Text("Intermediate")
}
}
}
}
}
}
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
This is sadly a SwiftUI bug, and it's even reproducible in vanilla SwiftUI. If you ever have a navigation hierarchy Screen A > Screen B > Screen C, such that Screen A has a navigation link and its body recomputes for any reason, then the navigation stack will be popped for some reason. For your particular situation you can hyper focus the struct RootView: View {
var store: StoreOf<Root>
var body: some View {
NavigationView {
Form {
WithViewStore(store, observe: { $0 }) { viewStore in
Text(String(viewStore.intermediate.counter.count))
}
NavigationLink {
IntermediateView(
store: store.scope(state: \.intermediate, action: Root.Action.intermediate))
} label: {
Text("Intermediate")
}
}
}
}
} That trick may not always be possible, but if it is, then it works. Separately, we highly recommend not doing |
Beta Was this translation helpful? Give feedback.
This is sadly a SwiftUI bug, and it's even reproducible in vanilla SwiftUI. If you ever have a navigation hierarchy Screen A > Screen B > Screen C, such that Screen A has a navigation link and its body recomputes for any reason, then the navigation stack will be popped for some reason.
For your particular situation you can hyper focus the
WithViewStore
to be only around the part that needs the state, in particular don't wrap theNavigationLink
inside it: