@BindableState shared between sibling States #1676
-
So I’m fairly new to TCA and we committed to using it in our Greenfield app right after you released the Reducer Protocol update, so that’s the context I’m currently in. I love the reducer protocol update btw. It’s beautiful.
In the above code my
So, this is my best attempt at something like this so far -- just passing in a Binding to the child-State when scoping the store down. Do you have an example of a better way of accomplishing this?
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
I dont know if I fully understand your question, I was able to come up with the following: The main idea was just to treat the sub state as computed property and relay the changes in the // MARK: - Parent
// MARK: Reducer
public struct Parent: ReducerProtocol {
public struct State: Equatable {
@BindableState var name: String
var childState: Child.State {
set {
self.name = newValue.name
}
get {
.init(self.name)
}
}
}
public enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
case childAction(Child.Action)
}
public var body: some ReducerProtocol<State, Action> {
BindingReducer()
Scope(state: \.childState, action: /Action.childAction) {
Child()
}
Reduce { state, action in
return .none
}
}
}
// MARK: View
struct ParentIntercepterView: View {
var store: StoreOf<Parent>
var body: some View {
WithViewStore(store) { viewStore in
VStack {
TextField("name", text: viewStore.binding(\.$name))
ChildView(store: store.scope(state: \.childState, action: Parent.Action.childAction))
ChildView(store: store.scope(state: \.childState, action: Parent.Action.childAction))
}
}
}
}
// MARK: - Child
// MARK: Reducer
public struct Child: ReducerProtocol {
public struct State: Equatable {
@BindableState
var name: String = ""
init(_ name: String = "") {
self.name = name
}
}
public enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
}
public var body: some ReducerProtocol<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .binding(\.$name):
return .none
case .binding:
return .none
}
}
}
}
// MARK: View
struct ChildView: View {
var store: StoreOf<Child>
var body: some View {
WithViewStore(store) { viewStore in
TextField(
"name",
text: viewStore.binding(\.$name)
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ParentIntercepterView(
store: .init(
initialState: .init(
name: "name"
),
reducer: Parent()
)
)
}
} |
Beta Was this translation helpful? Give feedback.
-
Hi @patch-benjamin, the approach mentioned by @ibrahimkteish above can work. It's a little bit of boilerplate to maintain the computed property, but it does work. We also have a case study to show this style off. There are a few other approaches. First off, if we look at the vanilla SwiftUI example you shared: @State var username = ""
var body: some View {
VStack {
EditUsernameView($username)
FancyEditUserNameView($username)
}
} If one of the examples in your app really is this simple, then you should know you can still use bindings to have a single source of truth but hand off little slices of that source of truth: @ObservedObject var viewStore: ViewStoreOf<Feature>
var body: some View {
VStack {
EditUsernameView(viewStore.binding(get: \.username, send: Action.setUserName))
FancyEditUserNameView(viewStore.binding(get: \.username, send: Action.setUserName))
}
} That works just like vanilla SwiftUI. The approaches above try to keep everything in state, which has its pros, but also cons (like the boilerplate). There is a 3rd approach that is a lot different from the above two. If the "shared state" is truly ubiquitous and global, then it may be best kept in a dependency. At that point it's not too unlike a database or user defaults. This dependency could expose synchronous access to the underlying data, and provide effectual endpoints for setting and subscribing to changes in the reducer. |
Beta Was this translation helpful? Give feedback.
Hi @patch-benjamin, the approach mentioned by @ibrahimkteish above can work. It's a little bit of boilerplate to maintain the computed property, but it does work. We also have a case study to show this style off.
There are a few other approaches.
First off, if we look at the vanilla SwiftUI example you shared:
If one of the examples in your app really is this simple, then you should know you can still use bindings to have a single source of truth but hand off little slices of that source of truth: