A Composable Architecture companion macro that generates the boilerplate necessary for routing a Reducer's Actions to function calls.
TCA is awesome! State management, testability, what's not to like!? That said, I often find myself a bit lost with all the enum cases, specifically in the Reducer's body. And while we can always split reducers up, sometimes there isn't a clean way of divvying up its responsibilities in a way that alleviates the problem.
What's causing this sense of disorientation? Is it the extra indentation? Is it the way each case is separated from one another? (Is it just me?) I don't know. To get to the bottom of this, we need to play around with some ideas!
The ReduceDispatcher
project explores one such idea: what if we could hide the Reduce's switch
statement while retaining all the pieces and concepts of TCA? Would it feel better if we replaced
all enum cases in a Reducer's body with function calls?
Well, let's find out!
Setting up the dispatcher is simple, though not entirely automatic. Take the following reducer:
@Reducer
struct MyReducer {
struct State {}
enum Action {
case didAppear
}
var body: some ReducerOf<Self> {
Reduce { _, action in
switch action {
case .didAppear:
print("foo")
}
return .none
}
}
}
All you need to do is:
- Import the
ReduceDispatcher
package - Add the
ReduceDispatcher
annotation - Replace
Reduce { ...
withDispatch(self)
- Conform your reducer to the autogenerated reducer
- The name is the name of your reducer, suffixed with
ActionDelegate
- Don't worry if this doesn't make sense. If you follow the previous step, the compiler will yell and tell you what to do.
import ReduceDispatcher
@Reducer
@ReduceDispatcher
struct MyReducer {
struct State {}
enum Action {
case didAppear
}
var body: some ReducerOf<Self> {
Dispatch(self)
}
}
extension MyReducer: MyReducerActionDelegate {
func didAppear(state: inout State) -> Effect<Action> {
print("foo")
return .none
}
}
That's it!
Sometimes you're scoping a bunch of child reducers, or are treating some actions as events. Having to
write out a function for those just to return .none
feels so bad, right? We gotchu! Just use the
@SkipDispatch
macro. Like so:
import ReduceDispatcher
@Reducer
@ReduceDispatcher
struct MyReducer {
struct State {}
enum Action {
case didAppear
@SkipDispatch
case didDisappear
}
var body: some ReducerOf<Self> {
Dispatch(self)
}
}
This library can be installed using the Swift Package Manager by adding it to your Package Dependencies.
- iOS 16.0+
- MacOS 13.0+
- Swift 5
- Xcoce 15.4+
Licensed under MIT license.