Swiftpack.co -  denizcoskun/RxStore as Swift Package
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
denizcoskun/RxStore
Reactive State Management Tool for Swift
.package(url: "https://github.com/denizcoskun/RxStore.git", from: "v0.1.0")

RxStore

RxStore is a fully reactive state management tool built on top of Apple's Combine framework. It is a naive implementation of Redux inspired by @ngrx/store.

Demo App

Spotify-Playlist-Sorter

Basic Usage


// Define your app store, it can have multiple sub states
class AppStore: RxStore {
    var counterState = RxStore.State(0)
}

// Define actions
enum CounterAction {
    struct Increment: RxStoreAction {}
    struct Decrement: RxStoreAction {}
}

// Create a reducer
let reducer : RxStore.Reducer<Int> = {state, action in
    switch action {
    case _ as CounterAction.Increment:
        return state + 1
    case _ as CounterAction.Decrement:
        return state - 1
    default:
        return state
    }
}

// Register the reducer and initialize the app store

let appStore = AppStore()
    .registerReducer(for: \.counterState, reducer)
    .initialize()

// You are ready to go

let cancellable = appStore
    .counterState
    .sink(receiveValue: {print($0)}) // 0, 1

appStore.dispatch(action: CounterAction.Increment)

App store with multiple states


// Define your app store, it can have multiple sub states
class AppStore: RxStore {
    var counterState = RxStore.State(0)
    var loadingState = RxStore.State(false)
}

// Define actions
enum CounterAction {
    struct Increment: RxStoreAction {}
    struct Decrement: RxStoreAction {}
}

enum LoadingAction {
    struct Loading: RxStoreAction {}
    struct Loaded: RxStoreAction {}
}


// Reducer for counter state
let counterReducer : RxStore.Reducer<Int> = {state, action in
    switch action {
    case _ as CounterAction.Increment:
        return state + 1
    case _ as CounterAction.Decrement:
        return state - 1
    default:
        return state
    }
}

// Reducer for loading state
let loadingReducer: RxStore.Reducer<Bool> = {state, action in
    switch action {
    case _ as LoadingAction.Loading:
        return true
    case _ as LoadingAction.Loaded:
        return false
    default:
        return state
    }
}

// Register the reducer and initialize the app store

let appStore = AppStore()
    .registerReducer(for: \.counterState, counterReducer)
    .registerReducer(for: \.loadingState, loadingReducer)
    .initialize()

// You are ready to go

let cancellable = appStore
    .counterState
    .sink(receiveValue: {print($0)}) // 0, 1

let cancellable2 = appStore
    .loadingState
    .sink(receiveValue: {print($0)}) // false, true

appStore.dispatch(action: CounterAction.Increment())
appStore.dispatch(action: LoadingAction.Loaded())

Usage with side effects


struct Todo {
 let id: Int
 let text: String
}

typealias TodosState = Dictionary<Int, Todo>

class AppStore: RxStore {
    var todosState = RxStore.State<TodosState>([:])
    var loadingState = RxStore.State(false)
}

enum Action {
    struct LoadTodos: RxStoreAction {}
    struct LoadTodosSuccess: RxStoreAction {
        let payload: [Todo]
    }
    struct LoadTodosFailure: RxStoreAction {
        let error: Error
    }
}

let todoReducer: RxStore.Reducer = {state, action -> TodosState in
    switch action {
    case let action as Action.LoadTodosSuccess:
        var newState = state
        action.payload.forEach {
            newState[$0.id] = $0
        }
        return newState
    default:
        return state
    }
}

let loadTodosEffect = AppStore.createEffect(Action.LoadTodos.self) { store, action in
    mockGetTodosFromServer()
        .map { Action.LoadTodosSuccess($0) }
        .catch {Just(Action.LoadTodosFailure(error: $0))}
        .eraseToAnyPublisher()
}



let store = AppStore()
    .registerReducer(for: \.todosState, reducer: todoReducer)
    .registerReducer(for: \.loadingState, reducer: loadingReducer)
    .registerEffects([loadTodosEffect])
    .initialize()

let cancellable = store.todosState.sink(receiveValue: {state in
            print(state) // [], ["mock-todo-id": MockTodo]
})

store.dispatch(Action.LoadTodos) // This will fetch the todos from the server 

Selectors

Selectors allow you to combine sub states and convert them into expected result.

Below is an example of how a selector can be used:

let todoList = [mockTodo, mockTodo2]
let userTodoIds: Dictionary<Int, [Int]> = [userId:[mockTodo.id], userId2: [mockTodo2.id]]

class AppStore: RxStore {
    var todos = RxStore.State(todoList)
    var userTodos = RxStore.State(userTodoIds)
}

let store = AppStore().initialize()

func getTodosForSelectedUser(_ userId: Int) -> AppStore.Selector<[Todo]> {
    AppStore.createSelector(path: \.todos, path2: \.userTodoIds) { todos, userTodoIds -> [Todo] in
        let todoIds = userTodoIds[userId] ?? []
        let userTodos = todos.filter { todo in  todoIds.contains(todo.id) }
        return userTodos
    }
}

let _ = store.select(getTodosForSelectedUser(userId2)).sink { userTodos in
    print(userTodos) // [mockTodo2]
}

GitHub

link
Stars: 5
Last commit: 5 weeks ago

Ad: Job Offers

iOS Software Engineer @ Perry Street Software
Perry Street Software is Jack’d and SCRUFF. We are two of the world’s largest gay, bi, trans and queer social dating apps on iOS and Android. Our brands reach more than 20 million members worldwide so members can connect, meet and express themselves on a platform that prioritizes privacy and security. We invest heavily into SwiftUI and using Swift Packages to modularize the codebase.

Related Packages

Release Notes

v0.1.0
5 weeks ago

Improve effects semantics

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API