Replies: 2 comments
-
Then again, you'd still have a problem with any state that was shared in multiple screens (for example a summary screen at the end.) Looks like there was another good conversation about this on the slack channel in addition to mine. |
Beta Was this translation helpful? Give feedback.
-
Chatting with @rhysm94 in that thread, he summed up the current options really well:
To me it seems like I do one of the following: 1. Wherein the child screen describes it's local state and shared state struct Screen1: Reducer {
struct LocalState {
var prop1: XXX
var prop2: XXX
var prop3: XXX
}
struct State {
var sharedProp: XXX
var local: LocalState
}
}
struct ParentReducer {
struct State {
var sharedProp: XXX
var sharedProp2: XXX
var screenState1: Screen1.LocalState
}
}
extension ParentReducer.State {
// getter and setter that build Screen1Reducer.State
var screenStatePackage: Screen1.State {
get {
.init (sharedProp: sharedProp, local: screenState1)
}
set {
sharedProp = newValue.sharedProp
screenState1 = newValue.screenState1
}
}
} This also assumes we've setup the path array as a computed getter/setter similar to how it's done in this TCACoordinators example, which is quite a bit of boilerplate code. 2. Wherein the shared state becomes a reference type with published properties Then we have the need to create a specific client for each screen to pull apart the larger dependency struct SizeDataClient {
var setSize: @Sendable (String) -> ()
var size: AnyPublisher<String, Never>
} Initialize the client from the larger dependency extension Listing {
var sizeClient: SizeDataClient {
.init(
setSize: { self.size = $0 },
size: self.$size
)
}
} and then sync the data both ways: case .onTask:
return .run { send in
for await size in sizeClient.$size.values {
await send(.setSize(size))
}
}
case let .sizeTapped(size):
state.size = size
return .fireAndForget {
await sizeClient.setSize(size)
} This also means passing the dependency down through the reducers, the primary state not being a value type, losing some of the testing/diffing tools you get with TCA, and potentially dealing with a flicker when the screens load while they async pull in the latest state. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
We chatted about this a little bit over in the slack channel, but I wanted to examine this in a more long-form setting.
I am building out a reducer for a wizard flow that creates an item listing. Throughout the flow, we save a draft of the item to the server, and ultimately at the end of which we post it in it's final form.
It's nice to think about this listing as a domain object, and easier to operate on that vs looping through all the collected child screen states, when we want to post the request.
It would be nice to design the code in this manner, such that the top level reducer owns the primary state that all the child pages are updating:
But obviously that won't work as designed, because even if I use a computed property to generate the paths that get pushed onto the stack, those updates aren't coming back to the parent.
I'm trying to decide what an elegant solution looks like here. Requirements would include:
Listing
dependency where they could update more than they should.It would be lovely to have the primary state in the reducer for all testing/tooling benefits, but I'm not sure there's a clean path for keeping the child path state up-to-date if the parent changes, and updating the parent from the child. The latter would be easy enough to do with delegate actions, and probably even makes the most sense in a wizard format where you don't update the listing until the user taps "Continue", but keeping the child in sync isn't pretty.
I imagine the answer here will probably be a dependency, but the ergonomics of having to create partial listing APIs for each page as to not pass a full listing dependency in, then subscribe to updates, seem not great. Also losing some of the benefits of the TCA tooling for testing state updates and seeing diffs.
I'm building this out as an tool for evangelizing TCA at our company, which is why I'm trying to figure out what the cleanest solution looks like. One thing I had considered is whether a identity + keypath API for StackState would work, where your
Path
is an enum without associated values, and you pass a readwrite keypath to the local state you want the child to mutate when you append the path. Or otherwise describe some sort of registry ahead of time, and only have to append thePath
enum itself.Beta Was this translation helpful? Give feedback.
All reactions