Replies: 5 comments 22 replies
-
It's hard to say without seeing some code, but are you familiar with this article on |
Beta Was this translation helpful? Give feedback.
-
Sounds like it a very "analogue" kind of bug then, which explains the weird behaviour. But what is the best way to do the decomposition? Is it fine to pass down the same store but use more limited view states in the view stores, or is it important to also limit the store? In my case that would often lead to having 2 or 3 different stores in the same view. |
Beta Was this translation helpful? Give feedback.
-
@tgrapperon Thank you for all explanations. I'm now in the process of decomposing all my stores, I used to think it was only a performance issue so I figured I could always do that later, but then these weird bugs started showing up. Anyway, I've seen indirect allusions to I personally prefer the other design, with an This seems potentially problematic, has this been thoroughly tested? To me it would feel safer with say PS If you ever redesign
I think it's a bit weird that the same thing is used to access state properties and do the sending. It would be especially nice if we could bring by the |
Beta Was this translation helpful? Give feedback.
-
This can be pasted into a project with TCA protocol-beta and will trigger the bug. I commented in 3 places where there are things that can be removed to stop the bug from occurring. Note that the main view store is only used in one place, and there it is stateless. import SwiftUI
import ComposableArchitecture
// MARK: Views
@main
struct TestApp: App {
private let store: StoreOf<Talking> = .init(initialState: .init(), reducer: Talking())
var body: some Scene {
WindowGroup {
WithViewStore(store.stateless) { viewStore in
ContentView(store: store)
.onAppear {
// Trigger 1 (see below)
viewStore.send(.launched)
}
}
}
}
}
struct ContentView: View {
let store: StoreOf<Talking>
// This will of course contain the wrong UUID when the app is launched
@State private var page: UUID = .init()
// Trigger 2: Removing this unused binding - no bug
let someBinding: Binding<Int> = .init(get: { 0 }, set: { _ in })
var body: some View {
VStack {
TabView(selection: $page) {
ForEachStore(store.scope(state: \.roomPages, action: Talking.Action.roomPage)) { roomPageStore in
RoomPageView(store: roomPageStore)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
Text("Showing: \(page)")
}
}
}
// ROOM PAGE
struct RoomPageView: View {
@ObservedObject private var viewStore: ViewStore<RoomPage.State, RoomPage.Action>
init(store: Store<RoomPage.State, RoomPage.Action>) {
self.viewStore = ViewStore(store)
}
var body: some View {
// Trigger 3: Remove the scrollview - no bug
ScrollView(showsIndicators: false) {
VStack {
Text(viewStore.name)
.font(.largeTitle)
Text(viewStore.id.uuidString)
}
.padding(100)
.onAppear {
roomPageViews[viewStore.name, default: 0] += 1
let count = roomPageViews[viewStore.name, default: 0]
if count > 1 { print("MULTIPLE \(viewStore.name) (#\(count))") }
}
.onDisappear {
roomPageViews[viewStore.name, default: 0] -= 1
let count = roomPageViews[viewStore.name, default: 0]
if count == 1 { print("Single \(viewStore.name)") }
}
}
}
}
var roomPageViews: [String: Int] = [:]
// MARK: States and Actions
struct Talking: ReducerProtocol {
struct State: Equatable {
// Trigger 1: If these are set here, and not in the reducer - no bug
var roomPages: IdentifiedArrayOf<RoomPage.State> = [] // = .init(uniqueElements: names.map(RoomPage.State.init))
}
enum Action: Equatable {
case roomPage(id: UUID, action: RoomPage.Action)
case launched
}
func reduce(into state: inout State, action: Action) -> Effect<Action, Never> {
switch action {
case .roomPage:
break
case .launched:
// Trigger 1: If these are not set here, but in the State init - no bug
state.roomPages = .init(uniqueElements: names.map(RoomPage.State.init))
}
return .none
}
}
let names: [String] = ["Alec", "Ares", "Armando", "Armani", "Benji", "Drake", "Dustin", "Edgar", "Edwin", "Finnegan"]
// Child State
enum RoomPage {
struct State: Identifiable, Equatable {
let id: UUID = .init()
let name: String
}
enum Action: Equatable { }
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello, I have a pretty complex app with a TabView, where I run into an issue where some (but not all!) of the tab pages are instantiated multiple times. I'm trying to whittle it down to a small sharable example but so far it's still pretty complex. Seemingly it needs a combination of ScrollViews in a TabView,
id
'd in a particular way, and with a view store present, to trigger the problem.The weird thing is that it seems that the mere presence of a viewStore as a property in the leaf view, without even referencing it, is enough to cause this (when all the other parts are there). So it's definitely something weird.
The question right now is how I can best debug this! For the moment I have have global counter dictionaries that are increased/decreased in the onAppear/onDisappears, what else can I bring to the fight?
Beta Was this translation helpful? Give feedback.
All reactions