Sending child actions directly from the parent to update the child #1952
-
I have a brief question regarding parent-child relationships and a possible method of updating the child from the parent. struct Parent: ReducerProtocol {
struct State: Equatable {
var child: Child.State
}
enum Action: Equatable {
case child(Child.Action)
}
var body: some ReducerProtocol<State,Action> {
Scope(state: \.child, action: /Action.child) {
Child()
}
Reduce { state, action in
return .none
}
}
}
struct Child: ReducerProtocol {
struct State: Equatable {
var text: String
}
enum Action: Equatable {
case update
}
var body: some ReducerProtocol<State,Action> {
Reduce { state, action in
switch action {
case .update:
state.text = // do validation or api calls etc. that is related to the Childs Domain
return .none
}
}
}
} And in my Parent's View, I want to have a Button that should trigger an action in the child. struct ParentView: View {
let store: StoreOf<Parent>
var body: some View {
WithViewStore(self.store) { viewStore in
Button(action: {
viewStore.send(.child(.update))
// or..
viewStore.send(.didPressButton) // in the reducer return Effect(value: .child(.update))
}, label: {
Text("Trigger Child Action")
})
}
}
} Now my question is, is this a common pattern to trigger updates in child views? |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 25 replies
-
Hi @iwt-sebastian-boldt, neither of the styles you have sketched out is recommended in TCA. First, actions should be named after what the user did in the UI, not what you want to do inside the reducer. So the action Further, you should not think of actions as just weird kinds of methods defined for your domain. They are not methods, but instead a concrete description of things that can happen in the UI, and sending actions is a lot more expensive than calling methods. We have an article that goes into detail in this topic. My suggestion for your set up is for the child state to expose a mutating helper method that can do your logic, including returning effects: extension Child.State {
mutating func update(…) -> Effect<Child.Action> {
…
}
} Note that because this is a method and not an action, it is appropriate to name it after what it does rather than what the user is doing. And because this is a simple method you can call it from anywhere, including the child or parent domains. For example, in the parent you could do the following: case .buttonTapped:
return state.child.update().map(Action.child) That will run the mutating This style gives you maximum flexibility since you can call |
Beta Was this translation helpful? Give feedback.
-
If something has to be run on an actor (any actor) then it needs to be
an effect but you should be able to synchronously read the value from
your device client in your mutating state function because the TCA store
runs on the main thread anyway.
On March 5, 2023, GitHub ***@***.***> wrote:
@mbrandonw <https://github.com/mbrandonw> this breaks down when the
dependency within child state's mutating function requires being run
in main thread, right?
I was thinking of doing this:
extension SplitScreen.State { @mainactor mutating func
updateOrientation() -> Effect<SplitScreen.Action> {
@dependency(\.deviceClient) var deviceClient: DeviceClient orientation
= deviceClient.interfaceOrientation return .none } }
However, I realize that deviceClient.interfaceOrientation has a
dependency on UIApplication (DependencyAdditions'
ApplicationDependency actually), whose properties need to be retrieved
from main thread. Although the fix for that would be to run it within
an Effect closure, that would mean I am now attempting to modify state
within a concurrent context, which is the biggest no-no ;)
—
Reply to this email directly, view it on GitHub
<https://github.com/pointfreeco/swift-composable-
architecture/discussions/1952#discussioncomment-5209262>, or unsubscribe
<https://github.com/notifications/unsubscribe-
auth/AAAAEZJTFU2RSSNSIBHBQUTW2T6JLANCNFSM6AAAAAAVLWLZP4>.
You are receiving this because you were mentioned.Message ID:
<pointfreeco/swift-composable-architecture/repo-
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Before, to refresh session data for our logged in user (which required firing off a lot of different API requests) we'd have a high-level reducer to manage all of the effects and state and you'd need to do something like: Hi @lukeredpath , I m still quite confuse with the solution for this statement, how do you pass the information from parent to the child if the case is like pulling to refresh that only happened in the parent domain but all the children need to have the information in order to update all the dependency inside child reducer?
and for what @mbrandonw said, I totally agreed because only the child know what happened to its components, thus calling the action from parent domain doesn't make sense, but it's a different case with pulling to refresh right? where all child domains rely on the parent. |
Beta Was this translation helpful? Give feedback.
-
Insights: Following partially Brandon's suggestion, I was trying to send one of the child actions in this mutating method, but the child state was not changing. So I did the inverse; I added the reducers mutating logic in this I also had to update the parent state after calling update() _ = item.update()
state.items.updateOrAppend(item) Hope it helps |
Beta Was this translation helpful? Give feedback.
Hi @iwt-sebastian-boldt, neither of the styles you have sketched out is recommended in TCA.
First, actions should be named after what the user did in the UI, not what you want to do inside the reducer. So the action
.didPressButton
is clear and concise because the user just did something in the UI. However,.update
is opaque and no one unfamiliar with the intricacies of the child domain will know when that action should be sent.Further, you should not think of actions as just weird kinds of methods defined for your domain. They are not methods, but instead a concrete description of things that can happen in the UI, and sending actions is a lot more expensive than calling methods. We have…