-
Hi, There is something I definitely do wrong when using TCA. In this exemple why when I click on import SwiftUI
import ComposableArchitecture
struct AppState : Equatable {
var home : HomeScreenState? = nil
}
enum AppAction {
case showHome
case home(HomeScreenAction)
}
struct HomeScreenState : Equatable {
var feature : FeatureScreenState? = nil
}
enum HomeScreenAction {
case showFeature
case feature(FeatureScreenAction)
}
struct FeatureScreenState : Equatable {
var counter: Int = 0
}
enum FeatureScreenAction {
case incrementCounter
}
let appReducer = Reducer<AppState, AppAction, Void>.combine([
homeReducer
.optional()
.pullback(
state: \AppState.home,
action: /AppAction.home,
environment: { }
),
appLocalReducer,
])
let appLocalReducer = Reducer<AppState, AppAction, Void> {
state, action, env in
switch action {
case .showHome:
state.home = HomeScreenState()
return .none
case .home:
return .none
}
}
let homeReducer = Reducer<HomeScreenState, HomeScreenAction, Void>.combine([
featureReducer
.optional()
.pullback(
state: \HomeScreenState.feature,
action: /HomeScreenAction.feature,
environment: { }
),
homeLocalReducer,
])
let homeLocalReducer = Reducer<HomeScreenState, HomeScreenAction, Void> {
state, action, env in
switch action {
case .showFeature:
state.feature = FeatureScreenState()
return .none
case .feature: return .none
}
}
let featureReducer = Reducer<FeatureScreenState, FeatureScreenAction, Void> {
state, action, env in
switch action {
case .incrementCounter:
state.counter += 1
return .none
}
}
var counterBodyAppScreen: Int = 0
var counterInstanceAppScreen: Int = 0
struct AppScreen: View {
let store: Store<AppState, AppAction>
init(store: Store<AppState, AppAction>) {
self.store = store
counterInstanceAppScreen += 1
}
var body: some View {
counterBodyAppScreen += 1
return WithViewStore(self.store) { viewStore in
Text("instance[\(counterInstanceAppScreen)]")
Text("body[\(counterBodyAppScreen)]")
Button(action: { viewStore.send(.showHome) }) {
Text("Enable Home")
}
Divider()
LazyVStack {
IfLetStore(
self.store.scope(
state: { $0.home },
action: AppAction.home
),
then: Home.init(store:)
)
}
}.debug("AppScreen - instance[\(counterInstanceAppScreen)] / body[\(counterBodyAppScreen)]")
}
}
var counterBodyHome: Int = 0
var counterInstanceHome: Int = 0
struct Home: View {
let store: Store<HomeScreenState, HomeScreenAction>
init(store: Store<HomeScreenState, HomeScreenAction>) {
self.store = store
counterInstanceHome += 1
}
var body: some View {
counterBodyHome += 1
return WithViewStore(self.store) { viewStore in
Text("instance[\(counterInstanceHome)]")
Text("body[\(counterBodyHome)]")
Button(action: { viewStore.send(.showFeature) }) {
Text("Enable Feature")
}
Divider()
LazyVStack {
IfLetStore(
self.store.scope(
state: { $0.feature },
action: HomeScreenAction.feature
),
then: Feature.init(store:)
)
}
}.debug("Home - instance[\(counterInstanceHome)] / body[\(counterBodyHome)]")
}
}
var counterBodyFeature: Int = 0
var counterInstanceFeature: Int = 0
struct Feature: View {
let store: Store<FeatureScreenState, FeatureScreenAction>
init(store: Store<FeatureScreenState, FeatureScreenAction>) {
self.store = store
counterInstanceFeature += 1
}
var body: some View {
counterBodyFeature += 1
return WithViewStore(self.store.scope(state: \.counter)) { viewStore in
Text("instance[\(counterInstanceFeature)]")
Text("body[\(counterBodyFeature)]")
LazyVStack {
Text("Counter: \(viewStore.state)")
Button(action: { viewStore.send(.incrementCounter) }) {
Text("Increment Counter")
}
}
}.debug("Feature - instance[\(counterInstanceFeature)] / body[\(counterBodyFeature)]")
}
}
@main
struct DebugTCAApp: App {
var body: some Scene {
WindowGroup {
AppScreen(
store: Store(
initialState: AppState(),
reducer: appReducer,
environment: ()
)
)
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 4 replies
-
Having a state struct and enum action per screen is definitely not a bad thing to do. TCA encourages it. It's the whole reason we have The problem you are seeing is due to the fact that in WithViewStore(self.store) { viewStore in This means when anything changes in WithViewStore(self.store.stateless) { viewStore in That should fix your over-rendering problem. If later you need to start observing some things in |
Beta Was this translation helpful? Give feedback.
Having a state struct and enum action per screen is definitely not a bad thing to do. TCA encourages it. It's the whole reason we have
.pullback
on reducers and.scope
onStore
🙂The problem you are seeing is due to the fact that in
AppScreen
you are observing all ofAppState
:This means when anything changes in
AppState
it will cause this view to be recomputed. However, in that view you never actually access the state from the view store. You only need to send actions. So, what you can do is chisel away all the state so that you are only observing a void value:That should fix your over-rendering …