Skip to content

manolofdez/ReduceDispatcher

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ReduceDispatcher

A Composable Architecture companion macro that generates the boilerplate necessary for routing a Reducer's Actions to function calls.

Motivation

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!

Usage

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:

  1. Import the ReduceDispatcher package
  2. Add the ReduceDispatcher annotation
  3. Replace Reduce { ... with Dispatch(self)
  4. 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!

Dispatch skipping

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)
    }
}

Installation

Swift Package Manager

This library can be installed using the Swift Package Manager by adding it to your Package Dependencies.

Requirements

  • iOS 16.0+
  • MacOS 13.0+
  • Swift 5
  • Xcoce 15.4+

License

Licensed under MIT license.