A basic tutorial on how to use React and Redux with Hooks to make an API call.
By Steve Carey - 7/16/2019
Finished Code: Github
React version 16.8 (released February 2019) introduced hooks. Hooks allow you to use state and lifecycle methods in functional components for shorter, cleaner code. And React-Redux version 7.1 (released June 2019) added hooks so now you can really use hooks in your React/Redux applications. So how do you use React and Redux with hooks with an API? Glad you asked. Let's walk through a short example.
This tutorial assumes you are familiar with React, Redux and APIs in general. So the explanations will be on what changes with React and Redux hooks.
Let's back up and start with a simple API call using React without hooks and without Redux. Set up an app using create-react-app. Make sure you have a current version. Running npm install create-react-app -g
will install it or upgrade it in your global environment to the latest version.
Then create an app and cd into it:
create-react-app react-redux-with-hooks && cd react-redux-with-hooks
Now you have a React app. To make sure it works run yarn start
and it should launch a basic React app to localhost:3000 in your default browser.
For this tutorial you can delete some of the boilerplate files in the src folder including: App.css, App.test.js, logo.svg. And in the index.css file you can delete all the css and add an import for the Bootstrap CDN so that it looks like:
// src/index.css
@import url('https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css');
Install the Axios package. Axios assists with making API calls.
yarn add axios
Change the App.js file code to make an API call to an API endpoint I made for this tutorial. It will return a list of mock articles using a React class component without hooks.
// src/App.js
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.state = { articles: [], };
}
async componentDidMount() {
try {
const response = await axios.get('https://www.techandstartup.com/mockapi/articles')
this.setState({articles: response.data});
}
catch(error) {
console.log('error', error);
}
}
render() {
return (
<div className="container">
<h2>Articles</h2>
{this.state.articles.map(article => {
return (
<div key={ article.id }>
<hr/>
<h4>{article.title}</h4>
<p>{article.content}</p>
</div>
);
})}
</div>
)
}
}
export default App;
Check your browser localhost:3000. It should display a list of mock articles from the API.
Now let's modify this to use React hooks. Check the package.json file to make sure that React is version 16.8 or higher. If not you need to upgrade it or hooks will not work.
Change the App.js file to:
// src/App.js import React, { useState, useEffect } from 'react'; #1 import axios from 'axios'; function App() { #2 const [articles, setArticles] = useState([]) #3 useEffect(function() { #4 async function getArticles() { try { const response = await axios.get("https://www.techandstartup.com/mockapi/articles"); setArticles(response.data); #5 } catch(error) { console.log('error', error); } } getArticles(); }, []); return ( <div className="container"> <h2>Articles</h2> {articles.map(article => { #6 return ( <div key={ article.id }> <hr/> <h4>{article.title}</h4> <p>{article.content}</p> </div> ); })} </div> ) } export default App;
Save the file then refresh your browser. You should still see the same articles, only now you have a functional component that uses React hooks instead of a class component that doesn't. Here's what you do that's different:
Now let's add Redux. Install the redux related packages we are going to use:
yarn add redux react-redux redux-thunk redux-logger
Since Redux is not specific to React, the React-Redux package bridges the two. Version 7.1 released in June 2019 added hooks so now you can use Redux with functional components. Redux-logger is a middleware package that logs redux actions and state changes in the browser console so it's useful for development. The redux-thunk package is required to use middleware packages with Redux.
As mentioned I am assuming you are already familiar with Redux so I will only focus on what has changed with Redux hooks. Wiring up Redux with your app is the same with or without hooks. Change your index.js file so it looks like the below. We are importing our Redux packages, creating a store that accesses our reducer, and wrapping our App component with react-redux's Provider component that includes the store.
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger'
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import rootReducer from './redux';
const store = createStore(rootReducer, applyMiddleware(thunk, logger));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Create a file to store your redux specific code. Normally you would have separate files for your actions and your reducers but we'll put them all together in a file called redux.js
touch src/redux.js
// src/redux.js
import { combineReducers } from 'redux';
// ACTIONS
const SET_ARTICLES = 'SET_ARTICLES';
export function setArticles(articles) {
return {
type: SET_ARTICLES,
articles: articles,
};
}
// REDUCERS
const initialState = { articles: [] }
function articlesReducer(state = initialState, action) {
switch (action.type) {
case SET_ARTICLES:
return action.articles;
default:
return state;
}
}
export default combineReducers({
articles: articlesReducer,
});
The actions and reducers are also the same with or without hooks. Some people prefer to put the API call function in with the Redux actions but I'll keep it with the component. As such the Redux code here is only what is needed to store the API data in the Redux store.
Now change the App.js file to the below:
// src/App.js import React, { useEffect } from 'react'; #1 import { useSelector, useDispatch } from 'react-redux' #2 import axios from 'axios'; import { setArticles } from './redux'; function App() { #3 const articles = useSelector((state) => state.articles) #4 const dispatch = useDispatch(); #5 useEffect(function() { #7a async function getArticles() { try { const response = await axios.get("https://www.techandstartup.com/mockapi/articles"); dispatch(setArticles(response.data)); #6 } catch(error) { console.log('error', error); } } getArticles(); }, [dispatch]); #7b if(articles.length) { #8 return ( <div className="container"> <h4>Articles</h4> {articles.map(article => { #9 return ( <div key={ article.id }> <hr/> <h4>{article.title}</h4> <small>id:{article.id}</small> <p>{article.content}</p> </div> ); })} </div> ) } else { return (<div>No Articles</div>) } } export default App;
const mapStateToProps = (state) => ({ articles: state.articles });
const mapDispatchToProps = { setArticles };
this.props.setArticles(response.data);
this.props.articles
when rendering our component like we would have using Redux without hooks.Refresh your browser and you should still see the same API data, only now you are getting it in a simple functional component that uses both React and Redux hooks.