Said in the previous
Asynchronous Action is a design pattern in which an asynchronous Action is not a specific Action, but a combination of synchronous actions.
It is important to understand asynchronous actions and middleware with this in mind.
Asynchronous Action
First, what are asynchronous actions?
- After the Action is sent, the Reducer immediately calculates the State, which is synchronous.
- After the Action is sent, the Reducer is executed after a period of time. This is asynchronous.
Redux (1) : The three-piece set (Action, Reducer, Store) introduces synchronous actions. The Reducer will dispatch an Action, and the Reducer will synchronously calculate the new State, thus causing the View update. The diagram below:
Asynchronous actions require dispatch of at least three types of actions compared to synchronous actions:
- Action requested to start
- Request a successful action
- Failed to request an action
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE'.error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS'.response: {... }}Copy the code
Now that you know asynchronous actions, how do you implement them? How can an action be intercepted after it is sent (store.dispatch()) and then automatically sent some time later?
Let’s take a look at the asynchronous Action workflow ⬇️.
Asynchronous data flow
The obvious difference to synchronous data flow is the addition of Middlewares.
In the asynchronous data stream, the Redux asynchronous request, we found that the store. Dispatch () was repackaged and new capabilities were added, and the actions were no longer sent directly to the reducer, but instead needed to be preprocessed by Middlewares.
Middleware
Actions of certain types can be captured by using the specified Middleware (for example, Ajax types can be captured by Redux-thunk), which are preprocessed through the API, and the results are dispatched to the Reducer.
Simply put, Middleware is a function that transforms store.dispatch. His mission was to:
- Intercept action
- To deal with the action
- Send out the action
Note that Actions launch standard Actions and Middlewares return standard Actions. (Think back to what I said earlier.)
Development practices
Let’s develop an asynchronous application that uses the Reddit API to get a list of reactJS related articles. It looks like this ⬇️
Dependencies we will use:
- Use AXIos to send the request
- Use redux-thunk to handle asynchronous actions
- Use the Redux-Logger to print and dispatch action logs for us
The preparatory work
- Create the React project
npx create-react-app my_redux-thunk
Copy the code
- Install redux & React-redux
yarn add --dev redux react-redux
Copy the code
- Install redux-thunk & redux-Logger
yarn add --dev redux-thunk redux-logger
Copy the code
- Install axios
yarn add axios
Copy the code
The entrance
index.js
import React from "react";
import ReactDOM from "react-dom";
import Root from "./containers/Root";
import "./index.css";
import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
<React.StrictMode>
<Root />
</React.StrictMode>.document.getElementById("root")); reportWebVitals();Copy the code
The Action Creators and Constants
store/actions.js
import axios from "axios";
export const FETCH_POSTS_REQUEST = "FETCH_POSTS_REQUEST";
export const FETCH_POSTS_SUCCESS = "FETCH_POSTS_SUCCESS";
export const FETCH_POSTS_FAILURE = "FETCH_POSTS_FAILURE";
// Request the action to start
function fetchPostsRequest() {
return {
type: FETCH_POSTS_REQUEST,
};
}
// Request a successful action
function fetchPostsSuccess(json) {
return {
type: FETCH_POSTS_SUCCESS,
posts: json.data.children.map((child) = > child.data),
receivedAt: Date.now(),
};
}
// Failed to request action
function fetchPostsFailure(error) {
return {
type: FETCH_POSTS_FAILURE,
error,
};
}
/* ** asynchronous Action ** returns a function */
function fetchPosts() {
return (dispatch) = > {
// Start action
dispatch(fetchPostsRequest());
return new Promise((resolve, reject) = > {
const doRequest = axios.get("https://www.reddit.com/r/reactjs.json");
doRequest
// Request successful action
.then((res) = > {
dispatch(fetchPostsSuccess(res.data));
resolve(res);
})
// Request failed action
.catch((err) = > {
dispatch(fetchPostsFailure(err.message));
reject(err);
});
});
};
}
function shouldFetchPosts(state) {
if (state.isFetching) {
return false;
} else {
return true; }}export function fetchPostsIfNeeded() {
return (dispatch, getState) = > {
// Determine whether fetch is required
if (shouldFetchPosts(getState())) {
returndispatch(fetchPosts()); }}; }Copy the code
Reducers
store/reducers.js
import {
FETCH_POSTS_REQUEST,
FETCH_POSTS_SUCCESS,
FETCH_POSTS_FAILURE,
} from "./actions";
export default function postsReducer(
state = {
isFetching: false,
items: [],
},
action
) {
switch (action.type) {
case FETCH_POSTS_REQUEST:
return {
...state,
isFetching: true.error: null};case FETCH_POSTS_SUCCESS:
return {
...state,
isFetching: false.items: action.posts,
lastUpdated: action.receivedAt,
error: null};case FETCH_POSTS_FAILURE:
return {
...state,
isFetching: false.error: action.error,
};
default:
returnstate; }}Copy the code
Store
store/store.js
import { createStore, applyMiddleware } from "redux";
import thunkMiddleware from "redux-thunk";
import logger from "redux-logger";
import postsReducer from "./reducers";
export default function configureStore(preloadedState) {
return createStore(
postsReducer,
preloadedState,
applyMiddleware(thunkMiddleware, logger)
);
}
Copy the code
Container components
containers/AsyncApp.jsx
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchPostsIfNeeded } from ".. /store/actions";
import Picker from ".. /components/Picker";
import Posts from ".. /components/Posts";
class AsyncApp extends Component {
constructor(props) {
super(props);
this.handleFetch = this.handleFetch.bind(this);
}
componentDidMount() {
this.handleFetch();
}
handleFetch() {
this.props.fetchPostsIfNeeded();
}
render() {
const { items, isFetching, lastUpdated, error } = this.props;
return (
<div>
<Picker onFetch={this.handleFetch} />
<p>
{error && <span style={{ color: "red}} ">Fail To Load: {error}</span>}
</p>
<p>
{lastUpdated && (
<span>
Last updated at {new Date(lastUpdated).toLocaleTimeString()}.{" "}
</span>
)}
</p>{} {! isFetching && items.length === 0 &&<h2>Empty.</h2>}
{items.length > 0 && (
<div style={{ opacity: isFetching ? 0.5 : 1}} >
<Posts items={items} />
</div>
)}
</div>); }}function mapStateToProps(state) {
const { isFetching, lastUpdated, items, error } = state || {
isFetching: true.items: [],};return {
items,
isFetching,
lastUpdated,
error,
};
}
const mapDispatchToProps = { fetchPostsIfNeeded };
export default connect(mapStateToProps, mapDispatchToProps)(AsyncApp);
Copy the code
contianers/Root.jsx
import React, { Component } from "react";
import { Provider } from "react-redux";
import configureStore from ".. /store/store";
import AsyncApp from "./AsyncApp";
const store = configureStore();
export default class Root extends Component {
render() {
return (
<Provider store={store}>
<AsyncApp />
</Provider>); }}Copy the code
Display components
components/Picker.js
import React, { Component } from "react";
export default class Picker extends Component {
render() {
const { onFetch } = this.props;
return (
<div>
<h1>Get a list of Reactjs related articles on Reddit</h1>
<button onClick={()= >OnFetch ("reactjs")}> Click my fetch list</button>
</div>); }}Copy the code
components/Posts.js
import React, { Component } from "react";
export default class Posts extends Component {
render() {
return (
<ul>
{this.props.items.map((item, i) => (
<li key={i}>{item.title}</li>
))}
</ul>); }}Copy the code
Redux series
- Redux (1) : Three pieces (Action, Reducer, Store)
- Redux (2) : Develop applications with React
- Redux (3) : Middleware and Asynchronous Action
- Redux (4) : How to organize “Action” and “Reducer” files?
- Redux (5) : How can immutable data be used to improve performance?