The TCAFeatureAction
defines a pattern for actions on TCA based on https://www.merowing.info/boundries-in-tca/
Its idea is to set a well defined specification for actions on TCA views, where ideally View and Internal actions should not go out of the feature scope.
Example:
enum ExampleAction: TCAFeatureAction {
enum ViewAction: Equatable {
case onTapLoginButton
}
enum DelegateAction: Equatable {
case notifyLoginSuccess
}
enum InternalAction: Equatable {
case loginResult(Result<String, NSError>)
}
case view(ViewAction)
case delegate(DelegateAction)
case _internal(InternalAction)
}
When you have a Child
flow inside a Parent
store and need to scope it is often expressed as an InternalAction
of the Parent
store.
enum ParentAction: TCAFeatureAction {
enum InternalAction: Equatable {
// ...
case child(ChildAction)
}
// ...
case view(ViewAction)
case _internal(InternalAction)
case delegate(DelegateAction)
}
This will be expressed like below:
// On the reducer
Scope(state: \.child, action: /Action.InternalAction.child) {
ChildFeature()
}
// On the view
struct ParentView: View {
let store: StoreOf<ParentReducer>
var body: some View {
// ...
ChildView(
store.scope(
state: \.childState,
action: /ParentAction.InternalAction.child
)
)
// ...
}
}
Bounding reducers is a protocol to define a standard when implementing reducers with Boundaries. We can have them on composed (reducers with body) or non-composed reducers.
The main idea relies on setting a standard for separating the actions based on its type on specific functions:
// To handle actions coming from the view
func reduce(into state: inout State, viewAction action: Action.ViewAction) -> Effect<Action>
// To handle actions that happen inside the reducer
func reduce(into state: inout State, internalAction action: Action.InternalAction) -> Effect<Action>
// To handle actions that where delegated to this reducer
func reduce(into state: inout State, delegateAction action: Action.DelegateAction) -> Effect<Action>
Example for non-composed reducers:
struct SomeFeature: BoundingReducer {
func reduce(into state: inout State, viewAction action: Action.ViewAction) -> Effect<Action> {
switch action {
...
}
}
func reduce(into state: inout State, internalAction action: Action.InternalAction) -> Effect<Action> {
switch action {
...
}
}
...
}
Example for composed reducers:
struct SomeFeature: ComposedBoundingReducer {
var body: some Reducer<State, Action> {
coreReducer
.ifLet(\.child, action: /Action.InternalAction.child) {
ChildFeature()
}
}
func reduce(into state: inout State, viewAction action: Action.ViewAction) -> Effect<Action> {
switch action {
...
}
}
...
}
You can add TCABoundaries to an Xcode project by adding it as a package dependency.
- From the File menu, select Add Packages...
- Enter "https://github.com/bocato/tca-boundaries" into the package repository URL text field