When using Redux in a React project, there are a few main libraries that are typically involved:
redux: This is the core Redux library that provides the basics for creating stores, distributing actions, and processing reducers.
react-redux: This library provides bindings between Redux and React, making it easy to use state from the Redux store in React components.
Installation of related libraries
npm install redux react-redux
# or
yarn add redux react-redux
Creating a Redux store
To use Redux, you first need to create a Redux store, which is a “container” for your application’s global state. Here is a simple example of a store setup:
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
export default store;
1. Using Redux in Functional Components
Using Redux in a functional component usually involves the following steps:
Connecting to the Redux store: Use theuseSelector
anduseDispatch
hooks provided byreact-redux
to connect to the Redux store.
Select State: Use theuseSelector
hook to select the state you want from the store.
Distribute action: Use theuseDispatch
hook to distribute action to update the state in the store.
1.1 Recommended project document structure
If the project is using Redux as the state manager, a recommended file organization is shown below:
src/
|-- actions/
| |-- index.js
|-- reducers/
| |-- index.js
| |-- someReducer.js
|-- components/
| |-- SomeComponent.js
|-- store/
| |-- index.js
|-- App.js
The document organization structure described above helps keep the project clear and maintainable:
actions
folder contains all action creators, which are functions used to create and distribute actions.
reducers
folder contains all the reducer functions that update the state of the application based on incoming actions.
components
folder contains all the React components that are responsible for rendering the UI and possibly interacting with the Redux store.
store
The folder contains the logic for creating the Redux store, which is a “container” for applying global state.
With this structure, it is easy to find and manage all the code related to state management, which improves the readability and maintainability of the code. At the same time, this structure also follows the principle of separation of concerns, which makes different types of code (such as actions, reducers, components, etc.) organized in different folders for easy development and maintenance.
In addition, the following sequence is recommended for building a Redux data stream: actions/index.js -> reducers/someReducer.js -> reducers/index.js -> store/index.js -> App.js -> components/SomeComponent.js
1.2 Code Examples
actions/index.js: building type enumerations and actionCreators
export const SOME_ACTION = 'SOME_ACTION';
export function someAction(payload) {
return { type: SOME_ACTION, payload };
}
reducers/someReducer.js: single reducer, three components type initialState switch
import { SOME_ACTION } from '../actions';
const initialState = { data: null };
export function someReducer(state = initialState, action) {
switch (action.type) {
case SOME_ACTION:
return { ...state, data: action.payload };
default:
return state;
}
}
reducers/index.js:combine reducer -> rootReducer
import { combineReducers } from 'redux';
import someReducer from './someReducer';
const rootReducer = combineReducers({
someReducer
});
export default rootReducer;
store/index.js: the core step, creating the store
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
export default store;
App.js: top-level injection
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import SomeComponent from './components/SomeComponent';
function App() {
return (
<Provider store={store}>
<SomeComponent />
</Provider>
);
}
export default App;
components/SomeComponent.js: data
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { someAction } from '../actions';
export function SomeComponent() {
const dispatch = useDispatch();
const data = useSelector(state => state.someReducer.data);
const handleClick = () => {
dispatch(someAction('New data'));
};
return (
<div>
<p>{data}</p>
<button onClick={handleClick}>Update Data</button>
</div>
);
}
1.3 Code Interpretation
actions/index.js: In this file, we define an action typeSOME_ACTION
and a corresponding action creator functionsomeAction
.SOME_ACTION
This is a string constant that identifies a specific action in the reducer. ThesomeAction
function takes apayload
parameter and returns an action object that contains two properties,type
andpayload
. The property identifies the type of the action, while the property contains the type of the action.type
Thepayload
property identifies the type of the action, and the property contains the data associated with the action.
reducers/someReducer.js: This file defines a reducer function calledsomeReducer
. This function takes the current state and an action object as arguments and updates the state based on the type of the action. In this example, if the type of the action isSOME_ACTION
, then the reducer returns a new state that contains the incomingpayload
data. If the action is not of typeSOME_ACTION
, then the reducer returns the original state.
reducers/index.js: In this file, we use Redux’scombineReducers
method to merge all of our reducers into a single root reducer. The advantage of this is that we can easily manage multiple reducers, and each reducer is responsible for managing only a portion of the application state. In this example, we only have one reducer,someReducer
, but we still usecombineReducers
to organize it so that we can easily add more reducers in the future.
store/index.js: In this file, we use the core functioncreateStore
in conjunction with rootReducer to create the root store and export it.
App.js: In this file, we introducereact
,Provider
ofreact-redux
,store
which we created, and ourSomeComponent
component.Provider
is a Redux component that makes the Redux store accessible through React’s context API at any level in the application component tree. We wrap the entire application by usingstore
as an attribute ofProvider
so that any component in the application can access thisstore
.
components/SomeComponent.js: In this React component file, we use two React-Redux hooks,useSelector
anduseDispatch
, to connect to the Redux store. TheuseSelector
hook is used to select the state fragment we need from the store, and theuseDispatch
hook is used to distribute the actions. Inside the component, we define ahandleClick
function which is called when the user clicks the button and distributes asomeAction
action which updates the state in the store. The rendering part of the component displays data based on the current state and a button that the user can click to trigger the state update.
2. Using Redux in class components
Using Redux in a class component usually involves the following steps:
Connecting to the Redux store: Use theconnect
function provided atreact-redux
to connect to the Redux store.
Select State: Select the desired state from the store in themapStateToProps
function.
Distribute action: Define a method to distribute an action in themapDispatchToProps
function to update the state in the store.
2.1 Recommended project document structure
For projects using class components and Redux, the recommended file organization remains similar to that of functional components:
src/
|-- actions/
| |-- index.js
|-- reducers/
| |-- index.js
| |-- someReducer.js
|-- components/
| |-- SomeComponent.js
|-- store/
| |-- index.js
|-- App.js
Maintaining the same file structure contributes to the uniformity and maintainability of the project.
2.2 Code Examples
The code in the actions/index.js and reducers/ folders is the same as the examples in the functional component, so it will not be repeated.
store/index.js: The code that creates the store also remains the same.
App.js: still need to use Provider
at the top level to wrap the app.
import { Provider } from 'react-redux';
import store from './store';
import SomeComponent from './components/SomeComponent';
class App extends React.Component {
render() {
return (
<Provider store={store}>
<SomeComponent />
</Provider>
);
}
}
export default App;
components/SomeComponent.js: Connect to the Redux store using the connect
function.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { someAction } from '../actions';
class SomeComponent extends Component {
handleClick = () => {
this.props.dispatchSomeAction('New data');
};
render() {
return (
<div>
<p>{this.props.data}</p>
<button onClick={this.handleClick}>Update Data</button>
</div>
);
}
}
const mapStateToProps = state => ({
data: state.someReducer.data
});
const mapDispatchToProps = dispatch => ({
dispatchSomeAction: payload => dispatch(someAction(payload))
});
export default connect(mapStateToProps, mapDispatchToProps)(SomeComponent);
2.3 Code Interpretation
components/SomeComponent.js: In this class component file, we use the connect
function to connect to the Redux store. The connect
function takes two arguments: mapStateToProps
and mapDispatchToProps
. mapStateToProps
It is a function that takes the state of the entire store and returns an object whose properties are passed to the component’s props. In this example, we select the someReducer.data
state from the store and pass it to the component as the data
property. mapDispatchToProps
It is also a function that receives a dispatch
function as an argument and returns an object whose properties are functions, and these functions call dispatch
to distribute actions. in this example, we define a dispatchSomeAction
function that receives a payload
argument and distributes a someAction
action. finally, we use the connect
function to apply these two mapping functions to our component, thus creating a component that is connected to the Redux store.
This means that you need to use the HOC to inject two properties into the original component’s props, one for data and one for dispatchSomeAction , which represent references to existing data and methods for modifying the state, respectively. As you can see, the class component is injected rather than hooked. But the basic steps are exactly the same!