Introduction

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.


React App without 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.


    React App with Hooks

    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:

    1. Import useState and useEffect from React to use the State and Effect hooks. Read the Hooks at a Glance for a quick summary.
    2. Make the App component a function instead of a class.
    3. useState: Remove the constructor function and set the initial state to an empty array with the useState hook. UseState is a two element array that contains the current state as the first element and a function to update it as the second. Here we're assigning the (const) variable "articles" to the current state value, and "setArticles" to the update function.
    4. useEffect: useEffect essentially replaces the lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount). The API call will change the state in our component. Code inside the useEffect method runs after the initial render of the component.
    5. Once we get the data from the API, instead of calling the setState method, we instead call the useState method (which we assigned to setArticles) to update the state (which we assigned to article).
    6. Because we are in a functional component and not in a class component we don't use "this" to represent the current instance of the class... because there is no class and there is no instance of that nonexistant class. Instead we call the useState variable articles directly.

    Redux with Hooks

    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;
    
    1. We'll still import the React useEffect hook (replacing componentDidMount) to make our asynchronous API call in. But we won't use the React useState hook because we'll be getting our state from Redux.
    2. Import react-redux's two new hooks, useSelector and useDispatch, that you can read about at react-redux.js.org/next/api/hooks.
    3. As you can see we are using Redux with a functional component, not a class component.
    4. The useSelector hook replaces the mapStateToProps method. Without hooks we would get state from Redux with something like this placed below the class component:
      const mapStateToProps = (state) => ({ articles: state.articles });
      That would get the state from the Redux store and assign it to the component's props object. Instead we are assigning a (const) variable called articles to the articles object in the Redux store. It's initial value is an empty array (see the articlesReducer in the redux.js file).
    5. The useDispatch hook returns a reference to the dispatch function from the Redux store. We use it to dispatch our setArticles action below. This replaces the mapDispatchToProps method which would have looked something like this:
      const mapDispatchToProps = { setArticles };
    6. Dispatch the setArticles action passing in the response from the API call. Without hooks this would have looked something like this:
      this.props.setArticles(response.data);
    7. We put our API call in the React useEffect hook which runs after the initial render of our App component. UseEffect takes a second argument for cleanup when you unmount (leave the component. You need to add anything that changes props or state to the cleanup array, in this case the dispatch.
    8. We are wrapping the return statement in a conditional. That's because the initial render of the component happens before the API call. Our Articles variable is thus empty and will cause the map method to throw an error.
    9. We assigned our articles state to the variable articles. So we no longer use 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.