Classes

The following classes are available globally.

  • Easiest way to implement a Middleware, with a single function that gives you all you need, and from which you can return an Effect.

    A MiddlewareEffect is a function providing an incoming Action, State and Context (dispatcher source, dependencies, cancellation closure) and expecting as result one or multiple effects that will eventually result in outgoing actions.

    An Effect is a publisher or observable type according to your reactive framework. It can be a one-shot effect, such as an HTTP request, an observation that gives back multiple values over time, such as CoreLocation or NotificationCenter, a Timer, a pure value already known or even an empty effect using the .doNothing constructor, if there’s no need for side-effects. Effect cannot fail, and its element/output/value type is the OutputAction generic of this middleware. The purpose is running tasks, creating actions as they respond and returning these actions over time back to the Store. When the Effect completes, it should send a completion event so the middleware will cleanup the resources.

    Cancellation: effects can optionally provide a cancellation token, which can be any hashable value. If another effect arrives in the same middleware instance having the very same cancellation token, the previous effect will be cancelled and replaced by the new one. This is useful in case you want to keep only the last request of certain kind running, but cancel any previous ongoing request when a new is dispatched. You can also explicitly cancel one or many effects at any point. For that, you will be given a toCancel closure during every action arrival within the Context (third parameter). Feel free to call cancellation at that point or even later, if you hold this toCancel closure.

    Examples

    Using Promises

    let someMiddleware = EffectMiddleware<ApiRequestAction, ApiResponseAction, SomeState, Void>.onAction { action, state, context in
      switch action {
      case .users:
        return .promise { completion in
          DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            completion(ApiResponseAction.someResponse("42"))
          }
        }
      case .somethingIDontCare:
        return .doNothing
      }
    }
    

    From Publisher

    typealias ApiFetchMiddlewareDependencies = (session: @escaping () -> URLSession, decoder: @escaping () -> JSONDecoder)
    
    let apiFetchMiddleware =
      EffectMiddleware<ApiRequestAction, ApiResponseAction, SomeState, ApiFetchMiddlewareDependencies>.onAction { action, state, context in
        switch action {
        case .users:
          return context.dependencies.urlSession
            .dataTaskPublisher(for: fetchAllUsersURL())
            .map { data, _ in data }
            .decode(type: [User].self, decoder: context.dependencies)
            .map { users in ApiResponseAction.gotUserList(users) }
            .replaceError(with: ApiResponseAction.errorRetrivingUserList)
            .asEffect
        case .user(id: UUID):
          // ..
        case .somethingIDontCare:
          return .doNothing
        }
      }.inject((session: { URLSession.shared }, decoder: JSONDecoder.init))
    

    Cancellation

    typealias ApiFetchMiddlewareDependencies = (session: @escaping () -> URLSession, decoder: @escaping () -> JSONDecoder)
    
    let apiFetchMiddleware =
      EffectMiddleware<ApiRequestAction, ApiResponseAction, SomeState, ApiFetchMiddlewareDependencies>.onAction { action, state, context in
        switch action {
        case let .userPicture(userId):
          return context.dependencies.urlSession
            .dataTaskPublisher(for: fetchPicture())
            .map { data, _ in ApiResponseAction.gotUserPicture(id: userId, data: data) }
            .replaceError(with: ApiResponseAction.errorRetrivingUserPicture(id: userId))
            .asEffect(cancellationToken: "image-for-user-\(userId)") // this will automatically cancel any pending download for the same image
                                                                     // using the URL would also be possible
        case let .cancelImageDownload(userId):
          return context.toCancel("image-for-user-\(userId)")        // alternatively you can explicitly cancel tasks by token
        }
      }.inject((session: { URLSession.shared }, decoder: JSONDecoder.init))
    
    See more

    Declaration

    Swift

    @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
    public final class EffectMiddleware<InputActionType, OutputActionType, StateType, Dependencies> : MiddlewareProtocol
    extension EffectMiddleware: Semigroup
    extension EffectMiddleware: Monoid where Dependencies == Void
  • A Store Projection made to be used in SwiftUI

    All you need is to create an instance of this class by projecting the main store and providing maps for state and actions. For the consumers, it will act as a real Store, but in fact it’s only a proxy to the main store but working in types more close to what a View should know, instead of working on global domain.

                ┌────────┐
                 Button │────────┐
                └────────┘                                                       ┏━━━━━━━━━━━━━━━━━━━━━━━┓
           ┌──────────────────┐            dispatch                                                                   ┃░
                 Toggle      │───┼────────────────────▶│            ─▶  │────────────▶┃                       ┃░
           └──────────────────┘            view event      f: (Event)  Action     app action                         ┃░
               ┌──────────┐                                                                                         ┃░
                onAppear │───────┘                                                                                    ┃░
               └──────────┘                                ObservableViewModel                                       ┃░
                                                                                                                       ┃░
                                                             a projection of       projection          Store         ┃░
                                                             the actual store                                          ┃░
                                                                                                                     ┃░
       ┌────────────────────────┐                                                                                      ┃░
                                                                                           ┌┃─                 ┃░
           @ObservedObject     │◀               ◀─            ◀─         State                ┃░
                                          view state     f: (State)  View       app state  Publisher             ┃░
       └────────────────────────┘                                        State                                    ┃░
                                                                               ┗━━━━━━━━━━━━━━━━━━━━━━━┛░
                                                                                              ░░░░░░░░░░░░░░░░░░░░░░░░░
    ┌────────┐ ┌────────┐ ┌────────┐
      Text     List   ForEach 
    └────────┘ └────────┘ └────────┘
    
    See more

    Declaration

    Swift

    @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
    open class ObservableViewModel<ViewAction, ViewState> : StoreType, ObservableObject