Coder Social home page Coder Social logo

cloe's Introduction

Cloe

CI Status GitHub tag (latest SemVer) License

Cloe is Redux on Combine for SwiftUI with excellent feng shui.

Setup your store

struct AppState {
  var appName = "Demo App"
  var age = 6
  var names = ["hank", "cloe", "spike", "joffrey", "fido", "kahlil", "malik"]

  static let initialValue = AppState()
}

enum AppAction: Action {
  case growup
}

typealias AppStore = Store<AppReducer>

Setup your reducer

func appReducer(state: inout AppState, action: Action) {
  guard let action = action as? AppAction else { return }
  switch action {
  case .growup:
    state.age += 1
  }
}

Instantiate your Store

// Create a store with the publisher middleware
// this middleware allows us to use `PublisherAction`
// later to dispatch an async action.
let store = AppStore(
  reducer: appReducer,
  state: .initialValue,
  middlewares: [createPublisherMiddleware()])

// Inject the store with `.environmentObject()`.
// Alternatively we could inject it with `.environment()`
let contentView = ContentView().environmentObject(store)

// later...
    window.rootViewController = UIHostingController(rootView: contentView)

(Optionally) add some convenience extensions to the store

These extensions improve the ergonomics of working with the store. With the built-in dispatch function we would normally dispatch with store.dispatch(AppAction.growup). With this dispatch extension we can do store.dispatch(.growup) instead.

The subscript extension allows us to avoid using a closure with SwiftUI views. For example, a button can be implemented with: Button("Grow up", action: store[.growup]).

extension Store {
  func dispatch(_ action: AppAction) {
    dispatch(action as Action)
  }

  subscript(_ action: AppAction) -> (() -> Void) {
    { [weak self] in self?.dispatch(action as Action) }
  }
}

Connect your SwiftUI View to your store

This is an example of injecting state using a state selector. Here were define the state selector inside of the View, but it can be defined anywhere.

struct MyView: View {
  var index: Int

  // Define your derived state
  struct MyDerivedState: Equatable {
    var age: Int
    var name: String
  }

  // Inject your store
  @EnvironmentObject var store: AppStore

  // Connect to the store
  var body: some View {
    Connect(store: store, selector: selector, content: body)
  }

  // Render something using the selected state
  private func body(_ state: MyDerivedState) -> some View {
    Text("Hello \(state.name)!")
  }
  
  // Setup a state selector
  private func selector(_ state: AppState) -> MyDerivedState {
    .init(age: state.age, name: state.names[index])
  }
}

If you want to connect to the state of the store without defining a selector, use ConnectStore instead. Note that ConnectStore does not currently skip duplicate states the way that Connect does.

Dispatching a simple action

Here's how you can dispatch a simple action:

    Button("Grow up") { self.store.dispatch(AppAction.growup) }
    
    // ... or ...
    
    Button("Grow up", action: store[AppAction.growup])

Or with the optional Store extension mentioned above:

    Button("Grow up") { self.store.dispatch(.growup) }

    // ...or...

    Button("Grow up", action: store[.growup])

Dispatching an async action with the publisher middleware

Below is a simple example, read more about publisher middleware here.

    Button("Grow up") { self.store.dispatch(self.delayedGrowup) }
    
  //...

  private let delayedGrowup = PublisherAction<AppState> { dispatch, getState, cancellables in
    Just(())
      .delay(for: 2, scheduler: RunLoop.main)
      .sink { _ in
        dispatch(AppAction.growup)
      }
      .store(in: &cancellables)
  }

Tracking async task progress with publisher dispatcher

Publisher dispatcher documentation.

How is it different from ReSwift?

  • ReSwift is battle tested.
  • ReSwift is being used in real production apps.
  • Cloe uses Combine Publishers instead of a bespoke StoreSubscriber
  • Cloe's Middleware is simpler than ReSwift's Middleware but achieves the same level of flexibility.
  • Cloe's combineMiddleware function is simpler and easier-to-read.
  • Cloe provides a slick way to connect your SwiftUI views.
  • Cloe does not have a skip-repeats option for the main Store state, but when you Connect it to a SwiftUI component it always skips repeated states (subject to change).

Why does the Store object conform to ObservableObject?

You may have noticed that Cloe's Store class conforms to ObservableObject. However, the Store does not contain any @Published properties. This conformance is only added to make it easy to inject your store with .environmentObject(). However, since we don't expose any @Published vars don't expect a view with

@ObservedObject var store: AppStore

to automatically re-render when the store changes. This design is intentional so you can subscribe to more granular updates with Connect.

Example

To run the example project, clone this repo, and open iOS Example.xcworkspace from the iOS Example directory.

Requirements

  • iOS 13
  • macOS 10.15
  • watchOS 6
  • tvOS 13

Installation

Add this to your project using Swift Package Manager. In Xcode that is simply: File > Swift Packages > Add Package Dependency... and you're done. Alternative installations options are shown below for legacy projects.

CocoaPods

If you are already using CocoaPods, just add 'Cloe' to your Podfile then run pod install.

Carthage

If you are already using Carthage, just add to your Cartfile:

github "gilbox/Cloe" ~> 0.3.0

Then run carthage update to build the framework and drag the built Cloe.framework into your Xcode project.

License

Cloe is available under the MIT license. See the LICENSE file for more information.

cloe's People

Contributors

tsbehlman avatar gilbox avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.