Skip to content

Reducer

Abhi Muktheeswarar edited this page Jul 7, 2021 · 1 revision
typealias Reducer<A, S> = (A, S) -> S

A reducer is a pure function that takes the current State and an Action to compute a new State. In Flywheel, the reducer lives inside StateReserve.

val reducer = reducerForAction<CounterAction, CounterState> { action, state ->
    with(state) {
        when (action) {
            is CounterAction.IncrementAction -> copy(counter = counter + 1)
            is CounterAction.DecrementAction -> copy(counter = counter - 1)
            is CounterAction.ResetAction -> copy(counter = 0)
            is CounterAction.ForceUpdateAction -> copy(counter = action.count)
            else -> state
        }
    }
}

There can be only one reducer associated with a StateReserve. However, we can combine multi reducers to create a root reducer. By having multiple child reducers, we can have a better separation of concerns. Flywheel comes with two helper functions to combine multiple reducers.

fun <S : State> combineReducers(vararg reducers: Reduce<S>): Reduce<S> =
    { action, state ->
        reducers.fold(state, { s, reducer ->
            reducer(action, s)
        })
    }
operator fun <S> Reduce<S>.plus(other: Reduce<S>): Reduce<S> = { action, state ->
    other(action, this(action, state))
}

Example:

val incrementReducer =
    reducerForAction<CounterAction.IncrementAction, CounterState> { _, state ->
        with(state) { copy(counter = counter + 1) }
    }

val decrementReducer =
    reducerForAction<CounterAction.DecrementAction, CounterState> { _, state ->
        with(state) { copy(counter = counter - 1) }
    }

val resetReducer = reducerForAction<CounterAction.ResetAction, CounterState> { _, state ->
    with(state) { copy(counter = 0) }
}

Using combineReducers

val rootReducer = combineReducers(incrementReducer, decrementReducer, resetReducer)

Using plus

val rootReducer = incrementReducer.plus(decrementReducer).plus(resetReducer)

If the number of actions that can update the state is less, you can just stick with one reducer.

Rules of reducers

  • A reducer should be a pure function. It means, reducer should only calculate the new state value based on the current State and Action.
  • Reducers are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values. (In Kotlin data class helps to achieve this with the copy function).
  • They must not do any asynchronous logic or other "side effects" like sending analytics event or logging something. Doing so, it violates the rules of a pure function.
  • Keep your reducers small by making use of combineReducers and plus helper functions.
  • Ideally, avoid iterating over large collections in a reducer, since it makes other actions to wait for a longer period of time in the queue. A better way to do is to, do the iteration and required updates in a SideEffect and dispatch an Action like UpdateMoviesListAction(movies) to set the updated collections.
Clone this wiki locally