RecoilSwift icon indicating copy to clipboard operation
RecoilSwift copied to clipboard

A New, Functional, Modern Reactive State Management Library for UIKit and SwiftUI (The iOS implementation of Recoil)

RecoilSwift

Version License Main workflow codecov

:closed_book: 中文文档

RecoilSwift is a lightweight & reactive swift state management library. RecoilSwift is a SwiftUI implementation of recoil.js which powered by Facebook.

RecoilSwift is an alternate option to replace of the Redux(reswift/tca) or MVVM.

What is recoil

Watch the video

State Management Data Flow

In recoil, there are mainly two concepts: atom and selector. atom is the primitive data(sync/async), selector is derived data(sync/async).

generally we put the business logic & UI login into selector.

  1. Recoil state is atomic, you can easily to compose and reuse state
  2. Recoil state is reactive. current selector will be recomputed when any of upstream state changed
  3. Recoil is shared, you can easy to reuse it in different component。

The tree pillar is really important. this is why recoil let you code more concise and more reusable.

<img src="image.png" width="700" height="378"/>

Requirements

  • iOS 13+
  • Xcode 13.2+

NOTE: Currently this library only support for SwiftUI

In recent release, we re-implement this library with react hooks pattern which making the usage of this lib is more similar with official way.

Installation

  1. In Xcode, open your project and navigate to FileSwift PackagesAdd Package Dependency...
  2. Paste the repository URL (https://github.com/hollyoops/RecoilSwift.git) and click Next.
  3. For Rules, select Branch (with branch set to master).
  4. Click Finish.

RecoilSwift is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'RecoilSwift'

Basic Usage

Create Atom / Selector:

// Create a Atom
let allBooksState = atom { [Book]() }

// Create readonly Selector
let currentBooksSelector = selector { get -> [Book] in
    let books = get(allBooksState)
    if let category = get(selectedCategoryState) {
        return books.filter { $0.category == category }
    }
    return books
}

// Create parameterized selector 
let someSelector = selectorFamily { (id: String, get: Getter) -> AnyPublisher<[String], Error> in
    // Do some logic in here with id
}

Use Atom / Selector in SwiftUI

Because the useRecoilXXX series API is based on Hooks. so it should follow all the rule of hooks

struct YourView: RecoilView { // You have to implement the RecoilView protocol
    var hookBody: some View { 
     let currentBooks = useRecoilValue(currentBooksSelector)

     let allBooks = useRecoilState(allBooksStates)

     let loadable = useRecoilValueLoadable(fetchRemoteDataByID(someID))
      // Your UI Code
    }
}

Advance Usage

You can use atomFamily/selectorFamily to execute the async tasks with customized parameter.

Async task above iOS 15

let fetchRemoteDataById = atomFamily { (id: String, get: Getter) async -> [String] in
   let posts = await fetchAllData()
   return posts[id]
}

// In some function
func someView() -> some View {
    HookScope { // when your view is not implement with RecoilView, you have to use `HookScope`
        let id = useRecoilValue(selectedCategoryState)
        let loadable = useRecoilValueLoadable(fetchRemoteDataById(id))
        
        // This body will be render after task completed
        return VStack {
            // while loading
            if loadable.isLoading {
                ProgressView()
            }

            // when error
            if let err = loadable.errors.first {
                errorView(err)
            }

            // when data fulfill
            if let names = loadable.data {
                dataView(allBook: names, onRetry: loadable.load)
            }
        }
    }
}

Below iOS 15 you can use Combine to run async tasks...

let fetchRemoteDataById = selectorFamily { (id: String, get: Getter) -> AnyPublisher<[String], Error> in
      Deferred {
        Future { promise in
              // Do some logic in here with id
        }
    }.eraseToAnyPublisher()
}

Documentation

API Reference

  • State

    • atom()
    • selector()
      • Readonly selector
      • Writeable selectors
      • Async selectors
  • Utils & Hooks

    • useRecoilValue()
    • useRecoilState()
    • useRecoilCallback()
    • useRecoilValueLoadable()
    • atomFamily()
    • selectorFamily()

Example

This is a easy demo, but we highly recommend you to check out the Example code. You'll see sharing states between different components is super easy. and the code become quite concise.

demo

TODOs

  • [ ] [performance]Remove unused recoil value in the store.
  • [ ] [feature]Make UIKit compatible

Reference: