We already implemented a unidirectional flow that accepts user actions and modifies the state, but what about async actions
which we usually call side effects. How to bake a support for async tasks into our store type? I think it is a good time to introduce the usage of Combine framework that perfectly fits async task processing.
typealias Reducer<State, Action, Environment> =
(inout State, Action, Environment) -> AnyPublisher<Action, Never>?
We add support for async tasks
by changing Reducer
typealias, it has the additional parameter called Environment
. Environment
might be a plain struct that holds all needed dependencies like service and manager classes.
func appReducer(
state: inout AppState,
action: AppAction,
environment: Environment
) -> AnyPublisher<AppAction, Never>? {
switch action {
case let .setSearchResults(repos):
state.searchResult = repos
case let .search(query):
return environment.service
.searchPublisher(matching: query)
.replaceError(with: [])
.map { AppAction.setSearchResults(repos: $0) }
.eraseToAnyPublisher()
}
return nil
}
Side Effect is a sequence of Actions
which we can publish using Combine
frameworkβs Publisher
type. It allows us to handle async job and then publish an action that will be used by reducer
to change the current state.
final class Store<State, Action, Environment>: ObservableObject {
@Published private(set) var state: State
private let environment: Environment
private let reducer: Reducer<State, Action, Environment>
private var cancellables: Set<AnyCancellable> = []
init(
initialState: State,
reducer: @escaping Reducer<State, Action, Environment>,
environment: Environment
) {
self.state = initialState
self.reducer = reducer
self.environment = environment
}
func send(_ action: Action) {
guard let effect = reducer(&state, action, environment) else {
return
}
effect
.receive(on: DispatchQueue.main)
.sink(receiveValue: send)
.store(in: &cancellables)
}
}
We build a Store
type that supports async tasks. Usually, reducer resolves an action by applying it on top of the state. In case of an async action, reducer returns it as Combine Publisher
, then Store run it and send result back to the reducer as a plain action.
Contacts
I have a clear focus on time-to-market and don't prioritize technical debt. And I took part in the Pre-Sale/RFX activity as a System Architect, assessment efforts for Mobile (iOS-Swift, Android-Kotlin), Frontend (React-TypeScript) and Backend (NodeJS-.NET-PHP-Kafka-SQL-NoSQL). And I also formed the work of Pre-Sale as a CTO from Opportunity to Proposal via knowledge transfer to Successful Delivery.
π©οΈ #startups #management #cto #swift #typescript #database
π§ Email: sergey.leschev@gmail.com
π LinkedIn: https://linkedin.com/in/sergeyleschev/
π LeetCode: https://leetcode.com/sergeyleschev/
π Twitter: https://twitter.com/sergeyleschev
π Github: https://github.com/sergeyleschev
π Website: https://sergeyleschev.github.io
π Reddit: https://reddit.com/user/sergeyleschev
π Quora: https://quora.com/sergey-leschev
π Medium: https://medium.com/@sergeyleschev
Top comments (2)
Thank you for the article. Can you explain how to return a few actions. For example, my system got a message from the server. I need to save it in the store, updated DB and make something else. It depends I should return 3 actions from the reducer in your article (in my projects I use middlewares). How do you solve this problem in your projects?
Thank you for your question.
Normalization and composition keep our app state simple and maintainable.
State normalization.
State composition.
Reducer composition.
Derived stores.
dev.to/sergeyleschev/redux-like-st...