Replies: 3 comments
-
Just as a data point while migrating an app I used the reducer (option 3) approach and it worked well thought it's quite ugly. |
Beta Was this translation helpful? Give feedback.
-
Great topic! I personally used a mix of 2 and 3: I passed a Approach 3 is the neatest in my opinion. This higher-order reducer is not in the TCA's domain in my mind. It's a kind of adapter between MVC and TCA. Furthermore, the subset of actions it will emit to the subject can be injected in the form of a predicate block In some way, the higher-order reducer acts like a kind of private and type-safe notification center. For example, with something like: public extension Reducer {
func relay(
actionPredicate: @escaping (Action) -> Bool,
destination: PassthroughSubject<Action, Never>
) -> Self {
combined(with: .init { _, action, _ in
if actionPredicate(action) {
return .fireAndForget {
destination.send(action)
}
}
return .none
}
)
}
} We can see that we're still returning a Of course, for this to work, you need to be able to make decisions from the actions only. If the state is needed to make decisions, maybe this can be expanded to a pseudo-predicate For example: public extension Reducer {
func notify<Notification>(
notifications: @escaping (State, Action) -> Notification?,
destination: PassthroughSubject<Notification, Never>
) -> Self {
combined(with: .init { state, action, _ in
if let notification = notifications(state, action) {
return .fireAndForget {
destination.send(notification)
}
}
return .none
}
)
}
} You can see this as a pseudo-pullback of the reducer in the MVC's world. [Edit: Refactored the higher-order reducers as methods of the original reducer instead of free functions] |
Beta Was this translation helpful? Give feedback.
-
Some really neat ideas here, thanks for sharing. I haven't been able to put all the pieces together, but I have wondered if one could encapsulate a TCA feature such that it could be initialized with a vanilla SwiftUI Binding. Obviously, that defers some important API design questions for dependencies, but still, interesting for the simple case of just synchronizing state. Maybe an extension on Binding producing a Store, or something? Could be useful for some low-level SwiftUI components, eg the DocumentGroup scene, where the scene wants to abstract away all the file effect stuff and just hand you a binding of your document. |
Beta Was this translation helpful? Give feedback.
-
Hello there!
Up until now I've always used the TCA in an entire app so I didn't find myself with these questions yet. But now we started using "feature packages" (pointfree style ^^) and integrating them into an existing codebase. So I'm finding myself wondering how to communicate those domains with the outside world, aka the main app.
So our app (uikit, mvvm, coordinators, etc...) reaches a point that wants to open a TCA feature. We create the store, the view, put it in a hosting controller and go! This is all fine. But then we need the TCA feature to tell the app to perform some actions (close this flow, navigate somewhere else, etc...).
Two conrecte scenarios are:
We've gone trough multiple approaches and I would like to know others opinion on this:
1. State driven communication
This was my first approach. Just put some state in your feature domain, and let the "app" code listen to it with the
ViewStore.publisher
.Something like
My problems with this approach are:
The last point is something that bothers me a lot. This and the fact that I feel like I"m using state changes as fake "events".
2. Callbacks in the environment
Another suggested approach is to just pass callbacks in the environment and let the feature domain use that.
My issues here are:
3. Listen for actions
Since what we want is to do something when an event happens, it would make sense to listen to actions and perform the changes in the app needed. The issue is that the Store/ViewStore doesn't expose a way to listen for actions, only state changes. So the way to do this is to combine the feature domain reducer with an on the fly reducer in the app to listen to actions.
The problems I see are:
Conclusion
When I'm trying to find a solution for this the question I ask myself is "How would this work if the app was already using full TCA". In that case you wouldn't pollute your feature with state that belongs to the parent (so no option 1), and you wouldn't use the environment to do things on the parent (so no option 2). You would actually make the parent have its own state and listen for actions from the children (option 3).
So option 3 is the best one in my eyes. And if the Store already provided a publisher for actions I wouldn't even be doubting myself 😂
To finish this text with specific questions:
Thanks! 🤗
Beta Was this translation helpful? Give feedback.
All reactions