I’ve been bugged by the native state management system in React that I finally had to take a stab at Redux. Here are some notes I took along the way to understand what Redux is and why we need it.

First off, why do we need Redux when we already have build-in states? Isn’t this just more boilerplate code? What’s the return on investment?

The short answer is it depends on the lifecycle of the data being stored in the state. In other words:

Pain Point

Let’s see how this might be a problem. There are cases where mismanaged states could cause chaos. For example: Pain Point

If Dropdown 1’s options wants to be conditionally dependant on Dropdown 2’s state or vice versa or both. And since props can only be passed from top to bottom, the only way either Dropdown knows the value of its sibling is to ask it’s parent (container). This means every action either dropdown invokes, it has to invoke an actionHandler at the parent level which sets the state for the other child which is then passed down to the child as a prop. This approach doesn’t scale well. Below is an example snippet of how this might be troublesome.

class Dropdown1 extends Component {
  
  render() {
    return (
      <select onChange={(e) => this.props.handler(e)} style=>
      //... various options
      </select>
    )
  }
}
class Dropdown2 extends Component {
  
  render() {
    return (
      <select onChange={(e) => this.props.handler(e)} style=>
      //... various options
      </select>
    )
  }
}
class Form extends Component {
  state = {
    dropdown1Active: true,
    dropdown2Active: true
  }
  handleAction1(e) {
    this.setState({dropdown2Active: false})
  }
  handleAction2(e) {
    this.setState({dropdown1Active: false})
  }
  render() {
    return (
      <Dropdown1 handler={this.handleAction1} isActive={this.state.dropdown1Active}/>
      <Dropdown2 handler={this.handleAction2} isActive={this.state.dropdown2Active}/>
    )
  }
}

Wouldn’t it be great if each child’s action can directly change the centralized state of a particular field without always going through the parent and all other children will know right away? That’s the simplified idea of Redux.


Before we go into the details of Redux, here are some commonly used terms that I’ll refer extensively in my notes.

Store{Object}—The centralized state storage. One instance exists at any given time; therefore, states that pertain to particular components will be stored under separate “keys”.

// Fetch the current store contents
store.getState()
// returns 
{
  todo: [{ id: 1, text: "test" }],
  counter: { currentNumber: 0 }
};

Reducer{Function}—Given the current state and action, returns the next state. This reminds me of the Markov Decision Process. Therefore, this function will contain the logic on how to update the states. By default, it will return the initial state currentNum: 0 if no action is provided. Similar to calling this.state in plain React. Always remember not to modify the state directly but to return a copy of the state {…state, currentNum: state.currentNum+1}(this is ES6 syntax).

// actions.js
function counter (state = { currentNum: 0 }, action) {
    switch (action.type) {
        case 'INCREMENT':
            return {...state,
                currentNum: state.currentNum+1
            }
        case 'DECREMENT':
            return {...state, 
                currentNum: state.currentNum-1
            }
        default:
            return state;
    }
}

The process: Define the reducer like above Create the store for a particular (or set of) reducers. You can include multiple reducers for a given store by using the combineReducers function provided by Redux. Using combineReducers will essentially combine multiple reducers into one reducer object, which saves the effort of redefining a parent reducer object.

// App.js
import { createStore, combineReducers } from 'redux';
/**
 * The keys used here will be the keys in the redux store
 * E.g.
 * {
 *    "todos": [],
 *    "counter": {
 *      "currentNum": 0
 *    },
 *    "filter": "SHOW_ALL"
 * }
 */
const app = combineReducers({
  todos,
  counter,
  filter
})
const store = createStore(app);
  1. Have the presentational components subscribe to the changes of the store to know when to re-render. This step could be replaced by connect() from react-redux package, which basically wraps around the component and all children under it will have access to the store instead of passing the store to every child that needs access to it. (Not explained in this note)
// index.js
import App, { store } from './App';
const render = () => {
    ReactDOM.render(<App />, document.getElementById('root'));
}
store.subscribe(render); // invokes render whenever the store changes, essentially pushing render into an array of listeners
  1. In the presentational components, reference the store and the contents that it’s interested in counter.currentNum
import { store } from './App';

<h3>{store.getState().counter.currentNum}</h3>

  1. Fire off an action from a button to dispatch a particular action for the reducer function to process
<button
  onClick={() =>
    store.dispatch({
      type: "INCREMENT"
    })
  }
>
  +
</button>

The “INCREMENT” action gets dispatched by the store to the reducer. In the reducer, the action and the current state gets processed by the logic, which then returns the next state {currentNum: state.currentNum + 1}

You can find a working example here that demonstrates and compares React Redux and Component State usage.