DEV Community

Andrew
Andrew

Posted on

Redux Data Flow and React Component Life Cycle

Almost everyone who wants to learn Redux had seen this image before. It's pretty straight forward for me right now, but it's hard to understand when I first learned Redux. To have a better understanding of the data flow in Redux. In this article, I will try to explain how Redux works with React. And also try to implement Redux through React hooks.

redux-flow

First, let start with Redux.

Redux is a state management system. Therefore, we will need:

  1. a place to save the state
  2. a method to get the state
  3. a method to change the state

And this is what we do when using Redux:
1.store is the place we save the state

import { createStore } from "redux";
import { reducer } from "./reduxModule";

const store = createStore(reducer);
Enter fullscreen mode Exit fullscreen mode

2.getState is the method to get the state

const state = store.getState();
Enter fullscreen mode Exit fullscreen mode

3.action & reducer is the method to change the mapStateToProps

const INCREMENT = "redux/increment";
const initialState = {
  counter: 0,
};

export const reducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return {
        counter: state.counter + action.amount
      };
    default:
      return state;
  }
};

export const incrementAction = (amount = 1) => {
  return {
    type: INCREMENT,
    amount,
  };
};
Enter fullscreen mode Exit fullscreen mode

The part we need to explain more will be action and reducer.
Redux update the state through action and reducer. The action tell reducer what does it want to do. Then the reducer updates the state base on the type and additional data provided by action.

Why use action and reducer?

I had discussed with lots of people why they are using Redux in their projects. Almost every time the answer will be - "easy to share props between components and prevent prop-drilling". I guess this is because back to the time we don't have stable context API, using Redux to share props seems to be a reasonable option. But in my opinion, it is not the core concept of Redux.
Using action and reducer to update the state can make it easier to control. The state can only be changed base on the actions we have defined. And all the logic about how the state should be changed is in the reducer. This can makes it easier to maintain.
The idea is like finite-state machine. If we want to add more state,
simply declare another action and add the logic into the reducer.
If you're interested to know more about state machines. You can check this post written by Kent C. Dodds.

Now, we can visualize the Redux like this.

  1. During the initial phase, the reducer received the initial state and return it. So we will get the initial state ({counter: 0}) in getState. redux-init
  2. During the update phase, we send an increment action (in redux, we call this dispatch) to the reducer, through the switch statement which we defined in the reducer, it will returns a new state ({counter: 0}). redux-update

Next, let's apply in React

When we want to implement Redux in the React, we also need three things:

  1. save store state in React
  2. get the state in React component
  3. dispatch action in React component

For item 1, react-redux have a component called Provider that can help us do this.

import { createStore } from "redux";
import { Provider } from "react-redux";

const store = createStore(reducer);

return (
  <Provider store={store}>
    <Container />
  </Provider>
)
Enter fullscreen mode Exit fullscreen mode

For item 2 & 3, react-redux provide another HOC call connect. It will turn the state and action into component props. So we will be able to use it in our React component.

import { connect } from "react-redux";
import { incrementAction } from "./reduxModule";

const mapStateToProps = state => ({ counter: state.counter });
const mapDispatchToProps = { incrementAction };
export default connect(mapStateToProps, mapDispatchToProps)(Comp);
Enter fullscreen mode Exit fullscreen mode

Now, our component is able to receive the state and dispatch action. Therefore, it's easy to finish our component like this.

import React from "react";

export default function Comp({ counter, incrementAction }) {
  function handleIncreaseOne() {
    incrementAction(1);
  }
  function handleIncreaseTen() {
    incrementAction(10);
  }
  return (
    <div>
      <span>{counter}</span>
      <div>
        <button onClick={handleIncreaseOne}>+1</button>
        <button onClick={handleIncreaseTen}>+10</button>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here is all the code for you to reference: https://github.com/oahehc/react-redux-example/tree/basic

After integrating Redux into React, the visualization should look like this.

react-redux-init
react-redux-update

Implement Redux through React hooks

Now we know how Redux helps us manage the state, so we can try to apply the same idea through React hooks.
(* This is just an example to demo the basic idea about Redux, please DON'T use it to replace Redux and React-Redux in your project. If you want to know more detail about Redux, you can check this tutorial create by Dan Abramov)

Just like what we did before, we can split into three items.

  1. a place to save the state -> context API
  2. a method to get the state in React component -> useContext
  3. a method to change the state in React component -> useContext & useReducer
// @ReduxModule.js : reducer and action
const INCREMENT = "redux/increment";

export function reducer(state, action) {
  switch (action.type) {
    case INCREMENT:
      return state + action.amount;
    default:
      return state;
  }
}

export function incrementActionCreator(dispatch) {
  return amount => {
    dispatch({
      type: INCREMENT,
      amount
    });
  };
}
Enter fullscreen mode Exit fullscreen mode
// @Provider.js : apply context API to save the state
import React, { useReducer } from "react";
import { reducer, incrementActionCreator } from "./ReduxModule";

export const ReduxContext = React.createContext();
const initialState = 0;
function ReduxProvider({ children }) {
  const [counter, dispatch] = useReducer(reducer, initialState);

  return (
    <ReduxContext.Provider
      value={{ counter, incrementAction: incrementActionCreator(dispatch) }}
    >
      {children}
    </ReduxContext.Provider>
  );
}

export default ReduxProvider;
Enter fullscreen mode Exit fullscreen mode
// @Comp.js : apply useContext to get state and action from Context
import React, { useContext } from "react";
import { ReduxContext } from "./Provider";

export default function Comp() {
  const { counter, incrementAction } = useContext(ReduxContext);

  function handleIncreaseOne() {
    incrementAction(1);
  }
  function handleIncreaseTen() {
    incrementAction(10);
  }
  return (
    <div>
      <span>{counter}</span>
      <div>
        <button onClick={handleIncreaseOne}>+1</button>
        <button onClick={handleIncreaseTen}>+10</button>
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Reference: https://github.com/oahehc/react-redux-example/tree/custom-redux

When we implement Redux through React hooks, we use useContext and useReducer. This will bring up the core concept of Redux:

  1. useContext : sharing state with multiple components
  2. useReducer : handling state by the state machine

Conclusion

Thanks for the reading. I hope this article will make Redux be easier to understand. If you have any question or feedback, please feel free to leave your comment.

--

Reference

Top comments (0)