In the previous section we talked about basics about Redux and a simple use case to use React-Redux. In this post I’m going to share about Asynchronous Actions in Redux (impure actions).
So a common scenario will be fetching data — before, after (when succeed), after(when fail). In each of this state, we will have data
, error
, loading
status updated.
So create the folder structure as below, and under each put:
//userType.jsexport const FETCH_USERS_REQUEST = "FETCH_USERS_REQUEST";
export const FETCH_USERS_SUCCESS = "FETCH_USERS_SUCCESS";
export const FETCH_USERS_FAILURE = "FETCH_USERS_FAILURE";//userAction.jsimport axios from "axios";
import {
FETCH_USERS_REQUEST,
FETCH_USERS_SUCCESS,
FETCH_USERS_FAILURE,
} from "./userTypes";export const fetchUsers = () => {
return (dispatch) => {
// It passes the dispatch method as an argument to the function,
// thus making it able to dispatch actions itself.
dispatch(fetchUsersRequest());
axios
.get("https://jsonplaceholder.typicode.com/users")
.then((response) => {
const users = response.data;
dispatch(fetchUsersSuccess(users));
})
.catch((error) => {
dispatch(fetchUsersFailure(error.message));
});
};
};export const fetchUsersRequest = () => {
return {
type: FETCH_USERS_REQUEST,
};
};export const fetchUsersSuccess = (users) => {
return {
type: FETCH_USERS_SUCCESS,
payload: users,
};
};export const fetchUsersFailure = (error) => {
return {
type: FETCH_USERS_FAILURE,
payload: error,
};
};//userReducer.js
import {
FETCH_USERS_REQUEST,
FETCH_USERS_SUCCESS,
FETCH_USERS_FAILURE,
} from "./userTypes";const initialState = {
loading: false,
users: [],
error: "",
};const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USERS_REQUEST:
return {
...state,
loading: true,
}; case FETCH_USERS_SUCCESS:
return {
loading: false,
users: action.payload,
error: "",
}; case FETCH_USERS_FAILURE:
return {
loading: false,
users: [],
error: action.payload,
};
default:
return state;
}
};export default reducer;
There are a few points to note down here:
Async action creator:
Note that in the `fetchUsers
function we returns a function instead of an object, this is because in store.js
we have imported the redux-thunk middleware.
import { createStore, applyMiddleware } from “redux”;
import rootReducer from “./rootReducer”;
import thunk from “redux-thunk”;const store = createStore(rootReducer, applyMiddleware(thunk));
Using this specific middleware, an action creator can return a function instead of an action object.
When an action creator returns a function, that function will get executed by the Redux Thunk middleware. This function doesn’t need to be pure; it is thus allowed to have side effects, including executing asynchronous API calls. Middleware like thunk basically wraps the store’s dispatch
method and allows you to dispatch something other than actions, for example, functions or Promises.
Note that there are 2 kinds of reducer:
- Pure Reducer action: triggers a reducer and changes state.
- Impure Async action: triggers an async action. This may call a Reducer action inside it.
Complex action payload:
We can do more complex stuff to action payload than we thought, instead of just taking some of it’s property, we can do things like :
data: users.data.children.map(child => child.data)
Middleware:
If you used express before, you might be familiar with middleware already. Redux middleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. People use Redux middleware for logging, crash reporting, talking to an asynchronous API, routing, and more.
For our async actions, Thunk middleware isn’t the only way to orchestrate asynchronous actions in Redux:
- You can use redux-promise or redux-promise-middleware to dispatch Promises instead of functions.
- You can use redux-observable to dispatch Observables.
- You can use the redux-saga middleware to build more complex asynchronous actions.
- You can use the redux-pack middleware to dispatch promise-based asynchronous actions.
- You can even write a custom middleware to describe calls to your API, like the real world example does.
And last but not least, if you are interested in how the UI integrates with our Redux part: below is the code for UserContainer
.js :
import React, { useEffect } from “react”;
import { connect } from “react-redux”;
import { fetchUsers } from “../redux”;function UsersContainer({ userData, fetchUsers }) {
useEffect(() => {
fetchUsers();
}, []);return userData.loading ? (
<h2>Loading</h2>
) : userData.error ? (
<h2>{userData.error}</h2>
) : (
<div>
<h2>Users List</h2>
<div>
{userData &&
userData.users &&
userData.users.map((user) => <p>{user.name}</p>)}
</div>
</div>
);
}const mapStateToProps = (state) => {
return {
userData: state.user,
};
};const mapDispatchToProps = (dispatch) => {
return {
fetchUsers: () => dispatch(fetchUsers()),
};
};export default connect(mapStateToProps, mapDispatchToProps)(UsersContainer);