Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/denizcoskun/rxstore
Reactive State Management Tool for Swift
https://github.com/denizcoskun/rxstore
combine ngrx ngrx-store redux rxswift selector state-management swift swiftui
Last synced: 27 days ago
JSON representation
Reactive State Management Tool for Swift
- Host: GitHub
- URL: https://github.com/denizcoskun/rxstore
- Owner: denizcoskun
- License: mit
- Created: 2021-02-10T19:46:30.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2021-04-05T22:39:29.000Z (over 3 years ago)
- Last Synced: 2024-03-14T16:43:48.768Z (8 months ago)
- Topics: combine, ngrx, ngrx-store, redux, rxswift, selector, state-management, swift, swiftui
- Language: Swift
- Homepage:
- Size: 45.9 KB
- Stars: 10
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# RxStore
RxStore is a fully reactive state management tool built on top of Apple's [Combine](https://developer.apple.com/documentation/combine) framework.
It is a naive implementation of [Redux](https://redux.js.org/) inspired by [@ngrx/store](https://ngrx.io/guide/store).## Demo App
[Spotify-Playlist-Sorter](https://github.com/denizcoskun/Spotify-Playlist-Sorter)## Basic Usage
```swift
// 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 = {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, 1appStore.dispatch(action: CounterAction.Increment)
```
## App store with multiple states
```swift
// 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 = {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 = {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, 1let cancellable2 = appStore
.loadingState
.sink(receiveValue: {print($0)}) // false, trueappStore.dispatch(action: CounterAction.Increment())
appStore.dispatch(action: LoadingAction.Loaded())```
## Usage with side effects
```swift
struct Todo {
let id: Int
let text: String
}typealias TodosState = Dictionary
class AppStore: RxStore {
var todosState = RxStore.State([:])
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:
```swift
let todoList = [mockTodo, mockTodo2]
let userTodoIds: Dictionary = [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]
}```