Andrew
Posted on January 26, 2020
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.
First, let start with Redux.
Redux is a state management system. Therefore, we will need:
- a place to save the state
- a method to get the state
- 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);
2.getState
is the method to get the state
const state = store.getState();
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,
};
};
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.
- During the initial phase, the reducer received the initial state and return it. So we will get the initial state ({counter: 0}) in getState.
- 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}).
Next, let's apply in React
When we want to implement Redux in the React, we also need three things:
- save store state in React
- get the state in React component
- 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>
)
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);
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>
);
}
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.
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.
- a place to save the state ->
context API
- a method to get the state in React component ->
useContext
- 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
});
};
}
// @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;
// @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>
);
}
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:
- useContext : sharing state with multiple components
- 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
Posted on January 26, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.