paint-brush
Redux Lifecycleby@zhirzh
18,809 reads
18,809 reads

Redux Lifecycle

by Shirsh Zibbu4mAugust 2nd, 2018
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

<strong><em>Disclaimer</em></strong><em>: This article is not an introduction to </em><a href="https://hackernoon.com/tagged/agile" target="_blank"><em>redux</em></a><em>. There are a number of good resources to get started with redux (see </em><a href="#73d6"><em>below</em></a><em>) and I suggest you begin with them. This article isn’t about react either. The term “lifecycle” is used in context of redux data flow and operations.</em>

Coins Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Redux Lifecycle
Shirsh Zibbu HackerNoon profile picture

Pure and Synchronous

Disclaimer: This article is not an introduction to redux. There are a number of good resources to get started with redux (see below) and I suggest you begin with them. This article isn’t about react either. The term “lifecycle” is used in context of redux data flow and operations.

In just three years, redux has become quite a popular choice among frontend developers as the state management library of choice. Even more so in the react ecosystem.

It’s amazing (and impressive, really) how small and simple the codebase is. And yet it can handle highly complex situations with easily. Perhaps this simplicity of redux is a major driving factor for its widespread popularity.

In the next part, we’ll explore how middleware affect the redux lifecycle and the data flow.

The Basic Redux Lifecycle

Internally, redux only works with synchronous data flow and doesn’t come with many “moving parts” or magic methods like some other solutions.

The most basic redux setup can be thought of as a single loop of state updates that is made up of just 2 components:

  • The state tree
  • The reducer function

Actions are accessory objects that are required to figure out how to update the state for the next iteration.

The reducer function takes in the previous state and the dispatched action as its arguments and returns the next state. If there are no changes needed, it returns the previous state as-is. Otherwise, it creates new state and returns it.

Reducer Decomposition

It is a common practice to split our state tree into multiple slices and write a separate reducer for each state slice. Actions may or may not be concerned with multiple state slices. This process of breaking our reducers into smaller and easier to understand pieces in a process called Decomposition.

Independent Reducer Lifecycles

Combining Reducers

We can now combine several independent reducers back into a single reducer (often called the “root” reducer) and create a redux store using it.

import { combineReducers } from 'redux'







import fooReducer from './foo'import barReducer from './bar'​const rootReducer = combineReducers({foo: fooReducer,bar: barReducer,})

Alternatively, we could exclude the ‘Reducer’ suffix from our imports to make the code lighter.







import foo from './foo'import bar from './bar'​const rootReducer = combineReducers({foo,bar,})

Basic Redux Lifecycle

When an action is dispatched, the root reducer receives it and passes the same action object to all reducers for them to react to it independently. However, instead of passing the entire state tree to each reducer, redux will only pass the reducer’s exclusive state slice. The return value from each reducer is then merged back into the complete state tree.

This is the basic redux architecture and it is the most common one as well. With careful planning and smart division of the the app state most small-scale apps can work with this architecture without any added complexities.

State Dependent Reducers

As your state tree grows complex, you may find yourself in situations where you need to share state between different reducers. A quick fix is to combine those reducers into one. However, doing so usually creates fat reducers that don’t scale well.

If we want keep our reducers separate, we need to figure out whether we need to share the current state slice or the updated version.

State Dependent Reducers — Current State

Since reducers are just functions, we can pass them any number of arguments as we want. If a reducer requires state slice from another reducer, we can pass it in as the third argument.

Current-State Dependent Reducers

Combining Reducers

We cannot combine dependent reducers using the combineReducers() utility that ships with redux.

Once you go past the core use case for combineReducers, it’s time to use more “custom” reducer logic — Redux docs

Since reducers are just functions, we can pass in the state of a different reducer as a third argument.






import foo from './foo'import bar from './bar'​function rootReducer(state, action) {const fooState = foo(state, action)const barState = bar(state, action, state.foo)





return {foo: fooState,bar: barState,}}

Combining Current-State Dependent Reducers

State Dependent Reducers — Updated State

Generally, when a reducer depends on another reducer’s state, it requires the updated state. Personally, I have never come across a situation where the old state is required. If you know of any such experiences, do share them in the comments.

Updated-State Dependent Reducers

Combining Reducers

The code looks almost the same as it was in the previous section. We have made only a small change. Instead of passing the old state in state.foo, we are now passing the updated state in fooState.






import foo from './foo'import bar from './bar'​function rootReducer(state, action) {const fooState = foo(state, action)const barState = bar(state, action, foo_State_)





return {foo: fooState,bar: barState,}}

Combining Updated-State Dependent Reducers

Dependence on updated state of other reducers means that there is a vertical hierarchy of reducer invocation. This doesn’t mean much for trivial cases but the idea does hint the possibility of chaining reducers together. However, reducer chaining will increase the code complexity exponentially.

Note: Another alternative to the “shared-slice updates” issue would be to simply put more data into the action. You can read more about it here.

Putting it all together

Now that we’ve seen some patterns of laying out reducers and combining them in isolation, let’s see what they look like in union. Here’s a list of key things to observe:

  • Each reducer receives its exclusive slice of state as the first argument
  • Each reducer receives the same action object as the second argument
  • Dependent reducers receive the complete state or other reducers’ state slice as subsequent arguments
  • All updated state slices are merged together into a single state tree
  • Dependent reducers can form an invocation chain
  • Redux uses reducers to perform state manipulations

Complete Redux Lifecycle

Addendum

The missing bits

In all of the drawings, I intentionally left out action creator(s) and the store to keep them clean and free from bloat. As you know:

  • An action creator is a plain function that create an action object.
  • The dispatch function accepts the action object and passes it to it’s store’s root reducer.
  • The store is the place where the combined state tree and the dispatch function live.
  • Each redux store has its own associated root reducer that consumes the action object and updates the store’s internal state.

Resources for starting out