API call in React with Redux using redux-thunk
Today we’ll show you how to implement API call in React with Redux using redux-thunk. In the previous article, we have explained the redux example step by step for beginners and now in this article, we teach you how to fetch data from an API using redux (React-Redux async example).
Basic API Call in React with Redux and Intro to Thunk Middleware, React-Redux Async example with Thunk middleware, Fetching data from an api using React/Redux, react redux api example, react redux fetch data from api, redux thunk hooks, redux dispatch promise, redux async await, for more complex asynchronous actions, which of the following would work?, simple react redux api example.
Checkout more articles on ReactJS
In order to implement API call or async function, we have to use middleware in the redux application. So in this article, we’ll use the redux-thunk npm package. Let’s begin with an example.
Example of the redux thunk
We’ll create an example where we call sample API to get data and display in table form. Store all the data coming from the API response into the redux store. We’ll get data from the store and display it on the Content component. Also manage the pagination in the Sidebar component.
From the Sidebar component, when we click on pagination then we’ll again place an API to get data based on given page number and the received API response will be stored in redux. So the Content component will update as the data gets changed.
We recommend you to check the previous article before starting this.
Steps to fetch data from an API using redux thunk
- Setting up the Redux Store
- Install dependency
- Update redux store configuration
- Create action types and actions
- Create and combine reducer
- Create async actions
- Update components and css
- Output
1. Setting up the Redux Store
First, let’s setup the redux store in React application. If you don’t know how to implement redux in ReactJS then refer to the link below.
How to implement redux in React.js2. Install dependency
As we discussed, we have to install a redux-thunk
package to complete the example. Run the script below for installation.
1 | npm i redux-thunk |
3. Update redux store configuration
Let’s begin with the store configuration. In order to use redux-thunk, we have to use applyMiddleware
from redux. Check below code for more configuration.
store.js
1 2 3 4 5 | import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import appReducer from './reducers'; export default createStore(appReducer, applyMiddleware(thunk)); |
4. Create action types and actions
We have to create a few more action types to create a demo.
- GET_USER_LIST_STARTED – Use it before API call
- GET_USER_LIST_SUCCESS – Use it when we received success response
- GET_USER_LIST_FAILURE – Use it on API failure response
Your file should look like below.
actions/actionTypes.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // increase counter export const INCREMENT = 'INCREMENT'; // decrease counter export const DECREMENT = 'DECREMENT'; // reset counter export const RESET = 'RESET'; // set counter title export const SET_COUNTER_TITLE = 'SET_COUNTER_TITLE'; // set tagline export const SET_TAGLINE = 'SET_TAGLINE'; // get user list - started export const GET_USER_LIST_STARTED = 'GET_USER_LIST_STARTED'; // get user list - success export const GET_USER_LIST_SUCCESS = 'GET_USER_LIST_SUCCESS'; // get user list - failure export const GET_USER_LIST_FAILURE = 'GET_USER_LIST_FAILURE'; |
Now create a userActions.js
file where we’ll define actions for three different types like Before API call, On API success response and On API failure response.
actions/userActions.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import { GET_USER_LIST_STARTED, GET_USER_LIST_SUCCESS, GET_USER_LIST_FAILURE } from "./actionTypes"; // to get the list of users - started export const getUserListStarted = () => { return { type: GET_USER_LIST_STARTED } } // to get the list of users - success export const getUserListSuccess = data => { return { type: GET_USER_LIST_SUCCESS, payload: { data } } } // to get the list of users - failure export const getUserListFailure = error => { return { type: GET_USER_LIST_FAILURE, payload: { error } } } |
5. Create and combine reducer
It’s time to create a user reducer in reducers
directory for three types of actions and combine it together.
reducers/userReducer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import { GET_USER_LIST_STARTED, GET_USER_LIST_SUCCESS, GET_USER_LIST_FAILURE } from "../actions/actionTypes"; // define initial state of user const initialState = { data: null, loading: false, error: null } // update store based on type and payload and return the state export default function common(state = initialState, action) { switch (action.type) { case GET_USER_LIST_STARTED: return { ...state, loading: true } case GET_USER_LIST_SUCCESS: const { data } = action.payload; return { ...state, data, loading: false } case GET_USER_LIST_FAILURE: const { error } = action.payload; return { ...state, error } default: return state } } |
6. Create async actions
To manage all types of async actions, we have to create a new directory name as asyncActions
and to place an API call, we’ll create user async action. Check the code below.
asyncActions/userAsyncActions.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import { getUserListStarted, getUserListSuccess, getUserListFailure } from "../actions/userActions"; // get user list export const getUserList = (page = 1) => async dispatch => { dispatch(getUserListStarted()); try { const res = await fetch(`https://reqres.in/api/users?page=${page}`); const data = await res.json(); dispatch(getUserListSuccess(data)); } catch (err) { dispatch(getUserListFailure(err.message)); } } |
7. Update components and css
We’ve to update two components to connect the user store.
Content component:
In this component, first we’ll get the user list on componentDidMount
and display those list in table form using the redux store.
pages/Content.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | import React, { Component } from 'react'; import { connect } from 'react-redux'; import { incrementCounter, decrementCounter } from "../actions/counterActions"; import { setTagline } from "../actions/headActions"; import { getUserList } from '../asyncActions/userAsyncActions'; class Content extends Component { constructor(props) { super(props); this.state = { tagline: this.props.tagline } } componentDidMount() { this.props.getUserList(); } render() { const { tagline } = this.state; const { incrementCounter, decrementCounter, counterObj, setTagline, userObj } = this.props; return ( <div className="main"> <table border="1"> <thead> <th>First Name</th> <th>Last Name</th> <th>Email</th> <th>Avatar</th> </thead> <tbody> {userObj.data && userObj.data.data.map((x, i) => <tr key={i}> <td>{x.first_name}</td> <td>{x.last_name}</td> <td>{x.email}</td> <td><img src={x.avatar} width="40" height="40" /></td> </tr>)} </tbody> </table> <div style={{ marginBottom: 20 }}> <h2>{counterObj.counterTitle}: {counterObj.count}</h2> <input type="button" className="btn" style={{ marginRight: 10 }} value="+1" onClick={incrementCounter} /> <input type="button" className="btn" value="-1" onClick={decrementCounter} /> </div> <div style={{ marginBottom: 20 }}> Tagline: <input type="text" className="tagline" value={tagline} onChange={e => this.setState({ tagline: e.target.value })} /> <input type="button" style={{ padding: '5px 7px', marginLeft: 10, width: 100 }} value="Set" onClick={() => setTagline(tagline)} /> </div> <h2>Lorem ipsum dolor</h2> <h5>quam pellentesque, Dec 10, 2018</h5> <div className="fakeimg" style={{ height: 200 }}>Image</div> <p>Nisi vitae suscipit..</p> <p>Semper quis lectus nulla at. Nullam ac tortor vitae purus faucibus ornare suspendisse. Nunc faucibus a pellentesque sit. Risus quis varius quam quisque id diam vel quam elementum. Ornare aenean euismod elementum nisi quis eleifend quam.</p> <br /> <h2>Placerat vestibulum</h2> <h5>elementum integer enim neque, Sep 21, 2018</h5> <div className="fakeimg" style={{ height: 200 }}>Image</div> <p>Bibendum est ultricies..</p> <p>Semper quis lectus nulla at. Nullam ac tortor vitae purus faucibus ornare suspendisse. Nunc faucibus a pellentesque sit. Risus quis varius quam quisque id diam vel quam elementum.</p> </div> ); } } const mapStateToProps = (state) => { return { counterObj: state.counter, tagline: state.head.tagline, userObj: state.user } } const mapDispatchToProps = { incrementCounter, decrementCounter, setTagline, getUserList } export default connect(mapStateToProps, mapDispatchToProps)(Content); |
Sidebar component:
We’ll manage the user table pagination in this component and get the new list by clicking on pagination through the redux store. Check the code below.
pages/Sidebar.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | import React, { Component } from 'react'; import { connect } from 'react-redux'; import { resetCounter } from "../actions/counterActions"; import { getUserList } from '../asyncActions/userAsyncActions'; class Sidebar extends Component { render() { const { resetCounter, counterObj, userObj, getUserList } = this.props; return ( <div className="side"> {userObj.data && userObj.data.total_pages && Array.from(Array(userObj.data.total_pages).keys()).map(x => x + 1).map(x => ( <input key={x} type="button" style={{ marginRight: 5 }} className={`btn${x === userObj.data.page ? ' active' : ''}`} value={x} onClick={() => getUserList(x)} /> ))} <br /> {userObj.loading && <div style={{ marginTop: 10 }}>Fetching user details...</div>} <h2>{counterObj.counterTitle}: {counterObj.count}</h2> <input type="button" className="btn" value="Reset Counter" onClick={resetCounter} /> <h2>Arcu bibendum</h2> <h5>Sit amet mattis vulputate</h5> <div className="fakeimg" style={{ height: 200 }}>Image</div> <p>Non blandit massa enim nec dui nunc mattis enim. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam nulla..</p> <h3>Massa enim</h3> <p>Lorem ipsum dolor sit ame.</p> <div className="fakeimg" style={{ height: 60 }}>Image</div><br /> <div className="fakeimg" style={{ height: 60 }}>Image</div><br /> <div className="fakeimg" style={{ height: 60 }}>Image</div> </div> ); } } const mapStateToProps = (state) => { return { counterObj: state.counter, userObj: state.user } } const mapDispatchToProps = { resetCounter, getUserList } export default connect(mapStateToProps, mapDispatchToProps)(Sidebar); |
Now Update the css to slightly change the style. Our goal is focusing on the functionality so we are writing less style for components.
App.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | * { box-sizing: border-box; } /* Style the body */ body { font-family: Arial, Helvetica, sans-serif; margin: 0; } /* Header/logo Title */ .header { padding: 40px; text-align: center; background: #269faf; color: white; } /* Increase the font size of the heading */ .header h1 { font-size: 40px; } /* Style the top navigation bar */ .navbar { overflow: hidden; background-color: #333; } /* Style the navigation bar links */ .navbar a { float: left; display: block; color: white; text-align: center; padding: 14px 20px; text-decoration: none; } /* Right-aligned link */ .navbar a.right { float: right; } /* Change color on hover */ .navbar a:hover { background-color: #ddd; color: black; } /* Column container */ .row { display: -ms-flexbox; /* IE10 */ display: flex; -ms-flex-wrap: wrap; /* IE10 */ flex-wrap: wrap; } .btn { cursor: pointer; line-height: 20px; font-size: 16px; font-weight: 600; padding: 10px 20px; } .btn.active { background-color: #333; color: #fff; } table { font-size: 13px; } .tagline { padding: 5px 7px; width: 300px; } /* Create two unequal columns that sits next to each other */ /* Sidebar/left column */ .side { -ms-flex: 30%; /* IE10 */ flex: 30%; background-color: #f1f1f1; padding: 20px; } /* Main column */ .main { -ms-flex: 70%; /* IE10 */ flex: 70%; background-color: white; padding: 20px; } /* Fake image, just for this example */ .fakeimg { background-color: #aaa; width: 100%; padding: 20px; } /* Footer */ .footer { padding: 20px; text-align: center; background: #ddd; } |
8. Output
Run your application and check the output.
That’s it for today.
Thank you for reading. Happy Coding!