Single source of truth eliminates tons of bugs produced by creating multiple states across the app. The main idea here is describing the whole app state by using a single struct or composition of structs. A single state for the whole app makes it easier to debug and inspect.
- Redux-like state container in SwiftUI. Basics.
- Redux-like state container in SwiftUI. Best practices.
- Redux-like state container in SwiftUI. Container Views.
import SwiftUI
import Combine
typealias Reducer<State, Action> = (inout State, Action) -> Void
final class Store<State, Action>: ObservableObject {
typealias Effect = AnyPublisher<Action, Never>
@Published private(set) var state: State
private let reducer: Reducer<State, Action>
private var cancellables: Set<AnyCancellable> = []
init(initialState: State, reducer: @escaping Reducer<State, Action>) {
self.state = initialState
self.reducer = reducer
}
func send(_ action: Action) {
reducer(&state, action)
}
func send(_ effect: Effect) {
var cancellable: AnyCancellable?
var didComplete = false
cancellable = effect
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [weak self] _ in
didComplete = true
if let effectCancellable = cancellable {
self?.cancellables.remove(effectCancellable)
}
}, receiveValue: send)
if !didComplete, let effectCancellable = cancellable {
cancellables.insert(effectCancellable)
}
}
}
extension Store {
func binding<Value>(
for keyPath: KeyPath<State, Value>,
_ action: @escaping (Value) -> Action
) -> Binding<Value> {
Binding<Value>(
get: { self.state[keyPath: keyPath] },
set: { self.send(action($0)) }
)
}
}