• React Redux Tutorial for 2019
  • Written by Dave Ceddia
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: xilihuasi
  • Proofread by: xionglong58, Fengziyin1234

2019 React Redux Complete Guide

Trying to understand how Redux works is a real headache. Especially as a beginner.

Too much jargon! Actions, Reducers, Action Creators, Pure Functions, Immutability, Thunks and so on.

How do you combine all of this with React to build a working application?

You can put it together by spending hours reading blogs and trying to learn from complex “real world” applications.

In this Redux tutorial, I’ll explain incrementally how to use Redux with React — starting with simple React — and a very simple React + Redux case. I’ll explain why each feature is useful (and when to make trade-offs).

And then we’ll move on to more advanced content, hand in hand, until you understand everything. Here we go 🙂

Please note: this tutorial is quite complete. That means the length is longer. I turned it into a full free course, and I created a beautiful PDF that you can read on an iPad or [any Android device]. Leave your email address for immediate access.

Video overview of Redux essentials

If you prefer watching videos to reading, this video covers how to add Redux step by step in the React app:

Video address

This is similar to the first part of this tutorial, where we will add Redux step by step in a simple React application.

Or, read on! This tutorial covers not only everything in the video, but also other dry stuff.

Should you use Redux?

It’s important to figure out if you should still be using Redux after 9102 years. Are there better alternatives now, using Hooks, Context or another library?

In short: Redux is not dead, even though there are alternatives. But whether it works for your application depends on the situation.

Super simple? How many states do you need in one or two places? Component internal state is fine. You can do this through classes, Hooks, or both.

A little more complicated, there are some “global” things that need to be shared across the application, right? The Context API might be perfect for you.

A lot of global states, interacting with individual parts of the application, right? Or a large application that only gets bigger over time? Try Redux.

You can also use Redux later; you don’t have to decide on day one. Start simple and add complexity when you need it.

Do you know React?

React can be used independently of Redux. Redux is an add-on to React.

Even if you plan to use them together, I highly recommend getting rid of Redux and learning pure React first. Understand props, state, and one-way data flow, and learn the React programming idea before learning Redux. Learning both at the same time will definitely confuse you.

If you want to get started with React, I’ve put together a free five-day course that teaches all the basics:

Spend the next five days learning React by building some simple applications.

The first lesson

The benefits of the story

If you’ve worked with React for a while, you probably know props and one-way data flows. Data is passed down the component tree through props. Like this component:

Count is stored in App state and passed down as prop:

To pass data up, you need to pass the callback function down, so you must first pass the callback function down to any component that wants to call it to pass data.

You can think of data as electricity, running through colored wires to components that need it. Data flows up and down wires, but wires cannot run through the air — they must be connected from one component to another.

Passing data at multiple levels is a pain

Sooner or later you’ll get into a situation where a top-level container component has some data and a child component above level 4 needs that data. Here’s an example from Twitter with all the avatars circled:

We assume that the state of the root component App has a User object. This object contains the current user’s profile picture, nickname, and other information.

In order to pass user data to all three Avatar components, it must pass through a bunch of intermediate components that do not require the data.

Getting data is like a mining expedition with a needle. Wait, that doesn’t make any sense. Anyway, it’s painful. Also known as “prop-drilling”.

More importantly, it’s not good software design. Intermediate components are forced to accept and pass props that they don’t care about. This means that refactoring and reusing these components is harder than it should be.

Wouldn’t it be great if components that don’t need this data didn’t have to see it at all?

Redux is one way to solve this problem.

Data transfer between adjacent components

If you have sibling components that need to share data, the React approach is to pass data up to the parent component and down through props.

But that can be troublesome. Redux provides you with a global “parent” that can store data, and then you can connect sibling components with data via React-Redux.

Use React-Redux to connect data to any component

Using the React-Redux connect function, you can insert any component into redux’s store and retrieve the required data.

Redux also does some cool things like making debugging easier (Redux DevTools lets you check every state change), time-travel debugging (you can roll back state changes to see what your app looked like before), and in the long run, It makes code easier to maintain. It will also teach you more about functional programming.

Built-in Redux replacement

If Redux is too much of a chore for you, check out these alternatives. They’re built into React.

Redux: The React Context API

At the bottom, React-Redux uses the React built-in Context API to pass data. If you want, you can skip Redux and use Context directly. You’ll miss the great features of Redux mentioned above, but if your application is simple and you want to pass data in a simple way, Context will do.

Now that you’ve read this far, I think you really want to learn about Redux. I’m not going to compare Redux to the Context API or use Context and Reducer Hooks here. You can follow the link to learn more.

If you want to dive deeper into the Context API, check out my lecture on React Context State Management at Egghead

Other alternatives: Usechildren Prop

Depending on how you build your application, you might pass data to child components in a more direct way, using children as “slots” combined with other props. If you organize it correctly, you can effectively skip several levels in the hierarchy.

I have a related article on the “slot” pattern and how to organize component trees to pass data efficiently.

Learn Redux, starting with simple React

We’ll take an incremental approach, starting with a simple React application with component state, adding Redux bit by bit, and resolving bugs encountered along the way. We call this “error-driven development” 🙂

Here is a counter:

In this example, the Counter component has state, and the App around it is a simple wrapper.

Counter.js

import React from 'react';

class Counter extends React.Component {
  state = { count: 0 }

  increment = (a)= > {
    this.setState({
      count: this.state.count + 1
    });
  }

  decrement = (a)= > {
    this.setState({
      count: this.state.count - 1
    });
  }

  render() {
    return (
      <div>
        <h2>Counter</h2>
        <div>
          <button onClick={this.decrement}>-</button>
          <span>{this.state.count}</span>
          <button onClick={this.increment}>+</button>
        </div>
      </div>)}}export default Counter;
Copy the code

A quick review of how it works:

  • countThe state is stored in theCountercomponent
  • The button is invoked when the user clicks “+”onClickProcessor executionincrementFunction.
  • incrementThe function updates the count value of state.
  • React will rerender because state has changedCounterComponent (and its children), which displays the new count.

If you want more details on how states change, check out the React State visual guide and come back here.

But let’s be honest: if this isn’t a review for you, you need to know how React state works before you learn Redux, or you’ll get really confused. Take my free 5-day React class, gain confidence with simple React, and then come back here.

To catch up with him!

The best way to learn is to try! So here’s a CodeSandbox you can follow:

–> Open CodeSandbox in a new TAB

I highly recommend that you keep CodeSandbox in sync with this tutorial and actually type out the examples as you go along.

Add Redux to the React application

In CodeSandbox, expand the Dependencies option on the left, then click Add Dependency.

Search for redux to Add dependencies, then click Add Dependency again to search for React-redux to Add.

NPM install –save redux react-redux.

redux vs react-redux

Redux gives you a store where you can save state, retrieve state, and respond when state changes. But that’s all it can do.

React-redux actually connects the states to the React component.

That’s right: Redux didn’t know anything about React.

These libraries, though, are like two peas in a pod. 99.999% of the time, when anyone mentions “Redux” in the React scene, they’re referring to these two libraries. So keep this in mind when you see Redux on StackOverflow, Reddit, or elsewhere.

The Redux library can be used independently of the React application. It can be used with Vue, Angular, and even backend Node/Express applications.

Redux has globally unique stores

We’ll start with a small piece of Redux: the Store.

We’ve already discussed how Redux stores your app’s state in a separate store. And how to extract part of state and embed it in your component as props.

You’ll often see the words “state” and “store” used interchangeably. Technically, state is data and store is where data is stored.

So: As the first step in our refactoring from simple React to Redux, we’ll create a store to keep state.

Create a Redux Store

Redux has a handy function for creating stores called createStore. Logical, huh?

We create a store in index.js. Introduce createStore and call it like this:

index.js

import { createStore } from 'redux';

const store = createStore();

const App = (a)= > (
  <div>
    <Counter/>
  </div>
);
Copy the code

“Expected the reducer to be a function.”

A Reducer is required for the Store

So here’s the thing about Redux: It’s not very smart.

You might expect that by creating a store, it will give your state an appropriate default value. Maybe an empty object?

But not so. There is no convention over configuration.

Redux doesn’t make any assumptions about your state. It could be an object, number, string, or whatever you need. It’s up to you.

We must provide a function that returns state. This function is called reducer (we’ll see why in a moment). So let’s create a very simple Reducer, pass it to createStore, and see what happens:

index.js

function reducer(state, action) {
  console.log('reducer', state, action);
  return state;
}

const store = createStore(reducer);
Copy the code

Once you’re done, open the Console (in the CodeSandbox, click the Console button at the bottom).

You should see logs like this:

(The letters and numbers after INIT are randomly generated by Redux)

Notice how Redux calls your reducer at the same time you create a store. (To verify this: Print console.log immediately after the createStore call and see what the reducer prints)

Also notice how Redux passes undefined state and action is an object with a Type attribute.

We’ll talk more about Actions later. Now, let’s look at reducer.

What is Redux Reducer?

The terminology “Reducer” may seem a little strange and scary, but after this section I think you will agree that, as the saying goes, “just a function”.

Have you used the reduce function for arrays?

Here’s how it works: You pass in a function that calls each element of the array, similar to what a map does — you might be familiar with maps from rendering lists in React.

Your function call takes two arguments: the result of the last iteration, and the current array element. It combines the current element with the previous “total” result and returns the new total value.

It will become clearer with the following example:

var letters = ['r'.'e'.'d'.'u'.'x'];

// 'reduce' accepts two arguments:
// - a reducer function (also called "reducer")
// - The initial value of a calculated result
var word = letters.reduce(
  function(accumulatedResult, arrayItem) {
    return accumulatedResult + arrayItem;
  },
' '); // <-- Notice the empty string: it is the initial value

console.log(word) // => "redux"
Copy the code

The function you passed to reduce should be called “reducer” because it reduces the array to a task.

Redux is basically a deluxe version of array Reduce. Earlier, you saw how Redux Reducers had this salient feature:

(state, action) => newState
Copy the code

Meaning: It receives the current state and an action, and returns newState. Look like reducer features in array. reduce!

(accumulatedValue, nextItem) => nextAccumulatedValue
Copy the code

Redux reducers works just like the function you passed to array.reduce! 🙂 they reduce actions. They reduce a set of actions (over time) into a single state. The difference is that Array’s Reduce occurs immediately, whereas Redux occurs throughout the life cycle of the running application.

If you’re still really unsure, check out my [Redux Reducers How it Works] guide (daveceddia.com/what-is-a-r…) . Otherwise, let’s move on.

Give the Reducer an initial state

Remember that the reducer’s job is to receive the current state and an action and return the new state.

It has another responsibility: it should return the initial state the first time it is called. It’s kind of like a “lead page” for your app. It has to start somewhere, right?

The conventional approach is to define an initialState variable and assign an initial value to state using the ES6 default parameter.

Now that we’re migrating Counter state to Redux, let’s create its initial state right away. In the Counter component, our state is an object with a count property, so we create the same initialState here.

index.js

const initialState = {
  count: 0
};

function reducer(state = initialState, action) {
  console.log('reducer', state, action);
  return state;
}
Copy the code

If you look at the console again, you’ll see that state prints the value {count: 0}. That’s what we want.

So this tells us an important rule about reducers.

Important Rule 1: Reducer must not return undefined.

In general, state should always be defined. A defined state is a good state. Undefined is not so good (and will break your application).

Dispatch Actions to change State

Yes, there are two names: we will “dispatch” some “actions”.

What is a Redux Action?

In Redux, ordinary objects with a type attribute are called actions. That’s it, it’s an action as long as you follow these two rules:

{
  type: "add an item".item: "Apple"
}
Copy the code

This is also an action:

{
  type: 7008
}
Copy the code

Here’s another one:

{
  type: "INCREMENT"
}
Copy the code

The format of Actions is very liberal. As long as it’s an object with a type attribute, it’s ok.

To ensure that transactions are legitimate and maintainable, we Redux users often assign simple strings to the type attribute of actions, usually in uppercase, to indicate that they are constants.

The Action object describes the change you want to make (such as “add counter”) or the event that will be triggered (such as “Request service failed and error message displayed”).

Despite their reputation, Actions are dull, wooden objects. They don’t actually do anything. They don’t do it themselves.

In order for the action to do something, you need dispatch.

Redux Dispatch working mechanism

The store we just created has a built-in function called Dispatch. Call with actions, Redux calls reducer with actions (the return value is then updated with state).

Let’s try it on store.

index.js

const store = createStore(reducer);
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });
store.dispatch({ type: "RESET" });
Copy the code

Add these dispatch calls to your CodeSandbox and check the console

Each call to Dispatch ends with a call to Reducer!

Also notice that the state is the same every time, right? {count: 0} has not changed.

This is because our Reducer does not work on those actions. But it’s easy to fix. Start now.

Process Actions in the Redux Reducer

To get actions to do something, we need to write a few lines of code in the Reducer to update the due state based on the type value of each action.

There are several ways to do this.

You can create an object to find the handler by action type.

Or you can write a bunch of if/else statements

if(action.type === "INCREMENT") {... }else if(action.type === "RESET") {... }Copy the code

Or you can use a simple switch statement, which I use below because it’s intuitive and a common method for this scenario.

Although some people hate switch, if you do too – feel free to write reducers any way you like 🙂

Here is our logic for handling actions:

index.js

function reducer(state = initialState, action) {
  console.log('reducer', state, action);

  switch(action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1
      };
    case 'DECREMENT':
      return {
        count: state.count - 1
      };
    case 'RESET':
      return {
        count: 0
      };
    default:
      returnstate; }}Copy the code

Try it out and see what comes out on the console.

Look! The count has changed!

Let’s talk about the Reducer code before we’re ready to connect to React.

How to keep pure Reducers

Another rule about reducers is that they must be pure functions. This means that their parameters cannot be modified, nor can they have side effects.

Reducer Rule 2: Reducers must be pure functions.

A “side effect” is any change to a function outside its scope. Do not change variables outside of function scope, do not call other functions that change (such as FETCH, related to networks and other systems), do not dispatch actions, etc.

Console. log is technically a side effect, but we’ll ignore it.

The most important thing: do not modify the state parameter.

This means you can’t do state.count = 0, state.items.push(newItem), state.count++, and other types of changes — don’t change state itself, or any of its subproperties.

You can think of it as a game where the only thing you can do is return {… }. It’s a fun game. It’s a little annoying at first. But you get better with practice.

I put together a complete guide on how to update Immutable in Redux, including seven general modes for updating objects and arrays in state.

Installing Immer for use in Reducers is also a good way to do this. Immer allows you to write normal mutable code, which eventually generates immutable code automatically. Click here to learn how to use Immer.

Tip: If you’re starting a brand new application, use Immer from the start. It will save you a lot of trouble. But I’m showing you this hard way because a lot of code still uses this way, and you’re bound to see reducers written without Immer

All the rules

Must return a state, don’t change state, don’t connect every component, eat broccoli, don’t go out after 11… It just goes on and on. It’s like a rule factory. I don’t even know what that is.

Yes, Redux is like an overbearing parent. But it was done out of love. Love of functional programming.

Redux is built on immutability, because changing global state is a path to ruin.

Have you tried saving your state in a global object? At first it was fine. Beautiful and simple. Anything can touch state because it’s always available and easy to change.

Then state began to change in unpredictable ways, and it became nearly impossible to find the code to change it.

To avoid these problems, Redux proposes the following rules.

  • State is read-only, and the only way to change it is by Actions.
  • The only way to update is dispatch(action) -> Reducer -> new state.
  • The Reducer function must be “pure” — it cannot modify its parameters or have side effects.

How to use Redux in React

At this point we have a small store with a Reducer that knows how to update state when an action is received.

Now it’s time to connect Redux to React.

To do this, you use two things from the React-Redux library: a component called Provider and a connect function.

By wrapping the entire application with a Provider component, every component in the application tree can access the Redux Store if it wants to.

In index.js, introduce a Provider and use it to wrap the content of your App. Store will be passed as prop.

index.js

import { Provider } from 'react-redux'; . const App =(a)= > (
  <Provider store={store}>
    <Counter/>
  </Provider>
);
Copy the code

And then, Counter, and the children of Counter, and the children of the children, and so on — all of these now have access to the Redux stroe.

But not automatically. We need to use the connect function in our component to access store.

The react-Redux Provider works

Providers can seem a little bit like magic. It actually uses the React Context property underneath.

Context is like a secret channel that connects each component, and you can open the secret channel door with connect.

Imagine pouring syrup on a pile of pancakes and the way it covers all of them, even if you only put the syrup on top. Provider does the same thing with Redux.

Prepare the Counter component for Redux

Now Counter has internal state. We’re going to kill it in preparation for getting count prop from Redux.

Remove the state initialization at the top and the setState calls within Increment and Decrement. Then, replace this.state.count with this.props. Count.

Counter.js

class Counter extends React.Component {
  // state = { count: 0 }; / / delete

  increment = (a)= > {
    /* / delete this.setState({count: this.state.count + 1}); * /
  };

  decrement = (a)= > {
    /* // delete this.setState({count: this.state.count - 1}); * /
  };

  render() {
    return (
      <div className="counter">
        <h2>Counter</h2>
        <div>
          <button onClick={this.decrement}>-</button>
          <span className="count">{// replace state: //// this.state.count // with: this.props. Count}</span>
          <button onClick={this.increment}>+</button>
        </div>
      </div>); }}Copy the code

Now Increment and Decrement are empty. We’ll fill them again soon.

You’ll notice that count has disappeared — and it should, as count Prop has not yet been passed to Counter.

Connect components to Redux

To get count from Redux, we first need to introduce the connect function at the top of counter.js.

Counter.js

import { connect } from 'react-redux';
Copy the code

Then we need to connect the Counter component to Redux at the bottom:

Counter.js

// Add this function:
function mapStateToProps(state) {
  return {
    count: state.count
  };
}

// Then add:
// export default Counter;

// Replace with:
export default connect(mapStateToProps)(Counter);
Copy the code

Previously we only exported the components themselves. Now we wrap it with the connect function call so we can export the connected Counter. As for the rest of the application, it looks like a regular component.

Then count should reappear! It won’t change until we re-implement Increment/Decrement.

How to use React Reduxconnect

You may notice that this call looks a bit… Strange. Why connect(mapStateToProps)(Counter) instead of connect(mapStateToProps, Counter) or connect(Counter, mapStateToProps)? What did it do?

This is written because connect is a higher-order function, which simply means that when you call it, it returns a function. When a component is passed in by the returned function, it returns a new (wrapped) component.

Another name for this is high-level component (“HOC” for short). HOCs has had some bad news in the past, but it’s still a pretty useful model, and Connect is a good example.

What Connect does is hook inside Redux, pull out the entire state, and pass it into the mapStateToProps function that you provide. It is a custom function because only you know the “structure” of the state you have in Redux.

Working mechanism of mapStateToProps

Connect passes the entire state to your mapStateToProps function, as if to say, “Hey, tell me what you want out of this pile.”

The object returned by mapStateToProps is passed to your component as props. In the example above, we pass the value of state.count as count prop: the properties of the object become the prop name, and their corresponding values become the props values. You see, this function literally defines the mapping from state to props.

By the way — the name of mapStateToProps is a convention, but not a specific one. You can abbreviate it mapState or you can call it however you want. As long as you take the state object and return an object that’s all props, that’s fine.

Why not pass the entire state?

In the example above, our state structure is already correct, and it seems that mapDispatchToProps may not be necessary. What does it mean if you essentially copy the argument (state) to an object that is the same as state?

In very small cases, the entire state might be passed, but typically you only select the data required by a component from a larger set of states.

Also, without the mapStateToProps function, connect does not pass any state.

You can pass the entire state and let the component comb through. But that’s not a good habit, because the component needs to know the structure of Redux state and pick out the data it needs, which makes it harder later if you want to change the structure.

Dispatch Redux Actions from the React component

Now that our Counter has been connected, we’ve got the count value. Now how do we dispatch actions to change count?

Well, connect supports you: besides passing (mapped) state, it also passes the dispatch function from the store!

To dispatch an action within the Counter, we can call this.props. Dispatch to carry an action.

Our Reducer is ready to handle INCREMENT and DECREMENT actions, so dispatch from INCREMENT/DECREMENT:

Counter.js

increment = (a)= > {
  this.props.dispatch({ type: "INCREMENT" });
};

decrement = (a)= > {
  this.props.dispatch({ type: "DECREMENT" });
};
Copy the code

Now we’re done. The button should be working again.

Try this! Add a reset button

Here’s an exercise: Add a “RESET” button to counter and dispatch a “RESET” action when clicked.

The Reducer has already written to handle this action, so you just need to modify counter.js.

The Action constants

In most Redux applications, you can see that action constants are simple strings. This is an extra level of abstraction that can save you a lot of time in the long run.

Action constants help you avoid typos. Action naming typos can be a huge pain: there are no errors, no obvious signs that something is broken, and your Action doesn’t do anything. That could be a typo.

Action constants are easy to write: hold your Action string in a variable.

Putting these variables in an actions.js file is a good idea (if your application is small).

actions.js

export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
Copy the code

You can then import these action names and use them instead of handwritten strings:

Counter.js

import React from "react";
import { INCREMENT, DECREMENT } from './actions';

class Counter extends React.Component {
  state = { count: 0 };

  increment = (a)= > {
    this.props.dispatch({ type: INCREMENT });
  };

  decrement = (a)= > {
    this.props.dispatch({ type: DECREMENT }); }; render() { ... }}Copy the code

What is the Redux Action generator?

We have now written the action object by hand. Like a pagan.

What if you had a function that would write it for you? Stop misspelling actinos!

It’s crazy, I can tell you. How hard is it to write {type: INCREMENT} by hand and not mess it up?

As your application gets bigger, your actions get bigger, and those actions get more complex — to carry more data than just a type — the action generator will help a lot.

Just like action constants, they are not required. This is another level of abstraction, and if you don’t want to use it in your application, that’s fine.

But I’ll explain what they are. Then you can decide if you sometimes/always/never want to use them.

An Actions generator is a simple function term in Redex terms that returns an action object. That’s it:)

These are two of them, returning familiar actions. By the way, they fit perfectly in the “actions.js” of the action constant.

actions.js

export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";

export function increment() {
  return { type: INCREMENT };
}

export const decrement = (a)= > ({ type: DECREMENT });
Copy the code

I’ve used two different approaches — a function and an arrow function — to show that it doesn’t matter which way you write. Just pick the way you like it.

You may have noticed that function names are lowercase (well, camel case for longer ones) and that the action constants are UPPER_CASE_WITH_UNDERSCORES. Again, this is just convention. This will allow you to distinguish action generators from action constants at a glance. But you can name it however you like. Redux doesn’t care.

Now, how do YOU use the Action generator? Import and dispatch, of course!

Counter.js

import React from "react";
import { increment, decrement } from './actions';

class Counter extends React.Component {
  state = { count: 0 };

  increment = (a)= > {
    this.props.dispatch(increment()); // << is used here
  };

  decrement = (a)= > {
    this.props.dispatch(decrement()); }; render() { ... }}Copy the code

The key is to remember to call Action Creator ()!

Don’t dispatch 🚫 (increment)

✅ should dispatch (increment ())

Keep in mind that the Action generator is a mundane function. Dispatch needs the Action to be an object, not a function.

Plus: You’re sure to get it wrong and very confused here. At least once, maybe many times. That’s normal. I still forget sometimes.

React Redux mapDispatchToProps

Now that you know what an Action generator is, we can discuss another level of abstraction. I know, I know. This is optional.)

Do you know how connect passes the dispatch function? You know how you get tired of banging on this.props. Dispatch all the time and how messed up it looks? (Come with me)

Write a mapDispatchToProps object (or function! Then pass it to the connect function that you want to wrap the component, and you’ll get those action generators as callable props. Look at the code:

Counter.js

import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from './actions';

class Counter extends React.Component {
  increment = (a)= > {
    // We can call 'increment' prop
    // It will dispatch the action:
    this.props.increment();
  }

  decrement = (a)= > {
    this.props.decrement();
  }

  render() {
    // ...}}function mapStateToProps(state) {
  return {
    count: state.count
  };
}

// In this object, the property name will be the names of prop,
// The property value should be an action generator function.
// They are bound to 'dispatch'.
const mapDispatchToProps = {
  increment,
  decrement
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
Copy the code

This is great because it frees you up from having to call dispatch manually.

You can write mapDispatch as a function, but the object will satisfy 95% of your scenarios. See functional mapDispatch and why you probably don’t need it.

How do I get data using Redux Thunk

Since reducers should be “pure”, we cannot make any API calls or dispatch actions in the Reducer.

We can’t do that in the Action generator either!

But what if we return an Action generator that can handle our work? Something like this:

function getUser() {
  return function() {
    return fetch('/current_user');
  };
}
Copy the code

Out of bounds, Redux does not support this kind of actions. Stubborn Redux only accepts simple objects as actions.

This is where redux-thunk comes in. It is middleware, basically a plug-in for Redux, that enables Redux to handle actions like getUser() above.

You can dispatch these “thunk actions” just like any other action generator: Dispatch (getUser()).

What is “thunk”?

“Thunk” is (rarely) a function that is returned by another function.

In Redux terms, it is an action generator that returns a function rather than a simple action object, like this:

function doStuff() {
  return function(dispatch, getState) {
    // Dispatch actions here
    // Or get data
    // Or do whatever you need to do}}Copy the code

Technically, the function returned is “thunk”, and the return value is “action generator”. I usually call them together “thunk actions.”

The function returned by the Action generator takes two arguments: the dispatch function and getState.

For most scenarios you just need dispatch, but sometimes you want to do something extra based on the values in the Redux state. In this case, you call getState() and you get the value of the entire state and take it as needed.

How do I install Redux Thunk

Install redux-thunk using NPM or Yarn. Run NPM install –save redux-thunk.

Then, in index.js (or wherever you create a store), introduce Redux-thunk and apply it to the Store via Redux’s applyMiddleware function.

import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';

function reducer(state, action) {
  // ...
}

const store = createStore(
  reducer,
  applyMiddleware(thunk)
);
Copy the code

Make sure thunk is wrapped in applyMiddleware calls or it won’t work. Don’t pass thunk directly.

Combine the Redux request data example

Imagine you want to present a list of products. You’ve got the backend API to respond to GET /products, so you create a thunk action to request data from the backend:

productActions.js

export function fetchProducts() {
  return dispatch= > {
    dispatch(fetchProductsBegin());
    return fetch("/products")
      .then(res= > res.json())
      .then(json= > {
        dispatch(fetchProductsSuccess(json.products));
        return json.products;
      })
      .catch(error= > dispatch(fetchProductsFailure(error)));
  };
}
Copy the code

Fetch (“/products”) is the part that actually requests the data. Then we made a few dispatch calls before and after it.

Dispatch Action to retrieve the data

To start the call and actually get the data, we need the Dispatch fetchProducts action.

Where do I call it?

If a particular component needs data, the best place to call it is usually right after the component is loaded, its componentDidMount lifecycle function.

Alternatively, if you are using Hooks, useEffect Hooks are a good place.

Sometimes you want to capture truly global data — such as “user information” or “internationalization” — that the entire application needs. In this scenario, use store.dispatch to dispatch the action after you create a store, rather than waiting for the component to load.

How do I name Redux Actions

Redux actions to retrieve data usually use a standard triad: BEGIN, SUCCESS, FAILURE. It’s not a requirement, it’s a rule.

The BEGIN/SUCCESS/FAILURE mode is great because it gives you hooks to track what’s going on — for example, set the “loading” flag to “true” in response to BEGIN, and set it to false after SUCCESS or FAILURE.

And, like everything else in Redux, this is a convention that you can ignore if you don’t need to.

Before you invoke the API, dispatch the BEGIN Action.

Once the call is successful, you can dispatch SUCCESS data. If the request fails, you can dispatch an error message.

Sometimes the last call is ERROR. It doesn’t really matter what you call, as long as you’re consistent.

Note: Dispatch Error actions to handle failures can cause you to track code without knowing that the action is correctly dispatched but the data is not updated. Learn my lesson 🙂

Here are the actions and their action generators:

productActions.js

export const FETCH_PRODUCTS_BEGIN   = 'FETCH_PRODUCTS_BEGIN';
export const FETCH_PRODUCTS_SUCCESS = 'FETCH_PRODUCTS_SUCCESS';
export const FETCH_PRODUCTS_FAILURE = 'FETCH_PRODUCTS_FAILURE';

export const fetchProductsBegin = (a)= > ({
  type: FETCH_PRODUCTS_BEGIN
});

export const fetchProductsSuccess = products= > ({
  type: FETCH_PRODUCTS_SUCCESS,
  payload: { products }
});

export const fetchProductsFailure = error= > ({
  type: FETCH_PRODUCTS_FAILURE,
  payload: { error }
});
Copy the code

After receiving the product data returned by the FETCH_PRODUCTS_SUCCESS action, we write a reducer and store it in the Redux store. Set the loading flag to true when the request is started and false when it fails or completes.

productReducer.js

import {
  FETCH_PRODUCTS_BEGIN,
  FETCH_PRODUCTS_SUCCESS,
  FETCH_PRODUCTS_FAILURE
} from './productActions';

const initialState = {
  items: [].loading: false.error: null
};

export default function productReducer(state = initialState, action) {
  switch(action.type) {
    case FETCH_PRODUCTS_BEGIN:
      // Mark state as "loading" so we can display spinner or other content
      // Again, reset all error messages. Let's start over.
      return {
        ...state,
        loading: true.error: null
      };

    case FETCH_PRODUCTS_SUCCESS:
      // Complete: Set loading to "false".
      // Again, assign the data obtained from the server to items.
      return {
        ...state,
        loading: false.items: action.payload.products
      };

    case FETCH_PRODUCTS_FAILURE:
      // The request failed. Set loading to "false".
      // Save the error message so we can display it elsewhere.
      // Since we failed, we have no products to show, so we need to clear 'items'.
      //
      // Of course it depends on you and the application:
      // Maybe you want to keep items data!
      // Whatever works for your scene.
      return {
        ...state,
        loading: false.error: action.payload.error,
        items: []};default:
      // Reducer requires a default case.
      returnstate; }}Copy the code

Finally, we need to pass the product data to the ProductList component that displays them and is also responsible for requesting the data.

ProductList.js

import React from "react";
import { connect } from "react-redux";
import { fetchProducts } from "/productActions";

class ProductList extends React.Component {
  componentDidMount() {
    this.props.dispatch(fetchProducts());
  }

  render() {
    const { error, loading, products } = this.props;

    if (error) {
      return <div>Error! {error.message}</div>;
    }

    if (loading) {
      return <div>Loading...</div>;
    }

    return (
      <ul>
        {products.map(product =>
          <li key={product.id}>{product.name}</li>
        )}
      </ul>); }}const mapStateToProps = state= > ({
  products: state.products.items,
  loading: state.products.loading,
  error: state.products.error
});

export default connect(mapStateToProps)(ProductList);
Copy the code

I mean data with state.products.

not just state.

, because I’m assuming you might have more than one reducer, each with its own state. To ensure this, we can write a rootreducer.js file and put them together:

rootReducer.js

import { combineReducers } from "redux";
import products from "./productReducer";

export default combineReducers({
  products
});
Copy the code

Then, when we create the store we can pass this “root” reducer:

index.js

import rootReducer from './rootReducer';

// ...

const store = createStore(rootReducer);
Copy the code

Error handling in Redux

The error handling here is light, but the basic structure is the same for most actions that call the API. The basic idea is:

  1. Dispatch a FAILURE action when the call fails
  2. Handle the FAILURE actions in the Reducer by setting some flag variables and/or saving error information.
  3. Pass error flags and information (if any) to the component that needs to handle the error, then render the error information in any way you see fit.

Can repeated rendering be avoided?

This is a common problem. Yes, it will trigger render more than once.

It first renders empty state, then loading state, and then renders the display product again. Terrible! Three renders! (You can render twice if you skip the “loading” state)

You might worry about unnecessary rendering affecting performance, but don’t: single rendering is very fast. If the app you’re developing is visually slow, analyze it and find out why.

Think of it this way: the application needs to show something when there are no items or things are loading or errors occur. You probably don’t want to just show a blank screen until the data is ready. This gives you an opportunity to provide a good user experience.

What’s next?

Hopefully this tutorial will help you understand Redux better!

The Redux documentation is full of great examples if you want to dive into the details. Mark Erikson’s blog, one of the Redux maintainers, has a nice series of commonly used Redux.

Next week, I’ll be releasing a new course, Pure Redux, that covers everything here, with more details:

  • How to update IMmutable
  • Immutable is easy to implement using Immer
  • Debug the application using Redux DevTools
  • Write unit tests for Reducers, Actions, and Thunk Actions

There’s a whole module on how to create a complete application, from start to finish, that includes:

  • Integrate CRUD operations with Redux — add, delete, review and change
  • Creating an API Service
  • Access to routing and request data when routing is loaded
  • Handle the modal dialog box
  • Combine multiple reducer with combineReducers
  • How to use selectors as wellreselectTo improve performance and maintainability
  • Permission and session management
  • The administrator view is separated from the common user view

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.