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?