DEV Community

Markus Claus
Markus Claus

Posted on • Updated on

Fetching data from an api using React/Redux

Starting simple

This is my first post here. I decided to share some of the knowledge I earned through making every mistake you can possibly make -.- Everything I write here I have learned by reading blog posts, trying to understand what was done and trial and error. If there are mistakes or if you think of a better way to do things, please let me know in the comments. I always appreciate helpful tips!

Now, first things first. You need to install React and Redux. I assume you know how to do that. After you've set up your React application you need to install a tool called redux-thunk by using the command npm install redux-thunk

With all those installed, we can now look at the components we're going to need to make the magic happen!

What is this thunk thing?

Basically a thunk is a function called by another function. Wait... What? Yeah, that's how I reacted the first time I heard this statement. Let me show you an example:

function some_function() {
    // do something
    return function thunk() {
        // do something thunky later
    }
}
Enter fullscreen mode Exit fullscreen mode

So, some_function is called, it does something and then it returns a new function with commands and possibly data for later execution.

Now what about Redux?

I don't want to go into the deepest depths of redux (most likely I couldn't anyway), so just a short explanation: It's a state container for javascript applications. It holds all the data you need for your application in one place. Every component within your application has its space in the state container where it looks for data. When the data changes, the component will change, too.

Actions

The idea is that you dispatch actions onto redux, and based on those actions the state is modified.

The funny thing is: An action doesn't do anything. It sounds like there is stuff going on, but there isn't. An action is just a plain object with a type key. Like this one:

// this is an action
{
    type: "SOME_ACTION",
    payload: {}
}
Enter fullscreen mode Exit fullscreen mode

Most of the time you don't want to write the same object over and over, so there is a concept called Action Creators.

Action Creators

Action Creators do exactly what they sound like, they create the action objects for you.

const SOME_ACTION = "SOME_ACTION";

function create_action(data) {
    return {
        type: SOME_ACTION,
        payload: data
    }
}
Enter fullscreen mode Exit fullscreen mode

So with those action creators you can now easily use the SOME_ACTION by calling create_action(data). Those action creators can be dispatched to redux by using dispatch(create_action(data)).

Reducers

After an action is dispatched, it will be passed onto a so called reducer. A reducer is a function which is given a state and an action. Depending on the action it will transform the state and then return the new state.

function someReducer(state, action) {
    switch(action.type) {
        case SOME_ACTION:
            return {
                ...state,
                data: action.payload
            }
        break;

        default:
            // the dispatched action is not in this reducer, return the state unchanged
            return state;
    }
}
Enter fullscreen mode Exit fullscreen mode

More complex applications most likely have multiple reducers, each one responsible for a single part of the state. So it is important to never forget the default case where the reducer returns the state unchanged.

Important to notice that reducers are pure functions. They never call something like an API or dispatch another action to redux.

You talked about thunks!?

You remembered that. Okay, thunks again. I just mentioned that reducers are pure. But often we want to have some kind of API call or dispatch something depending on data or whatever... But we can't... reducers are pure... Redux-Thunk to the rescue!

Redux-Thunk is pretty easy to understand. It is a so-called middleware for the redux store. It looks at every single action being dispatched and if it is a function, it calls the function. There is nothing more to it. But this opens up a whole new world of fancy "actions" which are dispatched to redux.

You might ask, how do I get this little wonder into my store?

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

import rootReducer from './rootReducer';
import initialState from './initialState';

const middlewares = [thunk];

createStore(rootReducer, initialState, applyMiddleware(...middlewares));

Enter fullscreen mode Exit fullscreen mode

Let's get some products

We want to load some products from our API. To do this, we first set our component in some kind of pending state, we show a loading spinner or something like that. Then we load the data and we decide whether or not we can just show the product list or display some kind of error message-

We start with setting up our action creators.


// action.js

export const FETCH_PRODUCTS_PENDING = 'FETCH_PRODUCTS_PENDING';
export const FETCH_PRODUCTS_SUCCESS = 'FETCH_PRODUCTS_SUCCESS';
export const FETCH_PRODUCTS_ERROR = 'FETCH_PRODUCTS_ERROR';

function fetchProductsPending() {
    return {
        type: FETCH_PRODUCTS_PENDING
    }
}

function fetchProductsSuccess(products) {
    return {
        type: FETCH_PRODUCTS_SUCCESS
        products: products
    }
}

function fetchProductsError(error) {
    return {
        type: FETCH_PRODUCTS_ERROR
        error: error
    }
}

Enter fullscreen mode Exit fullscreen mode

Now that we have our action creators, let's set up our reducer for the whole thing.


// reducer.js

import {FETCH_PRODUCTS_PENDING, FETCH_PRODUCTS_SUCCESS, FETCH_PRODUCTS_ERROR} from './actions';

const initialState = {
    pending: false,
    products: [],
    error: null
}

export function productsReducer(state = initialState, action) {
    switch(action.type) {
        case FETCH_PRODUCTS_PENDING: 
            return {
                ...state,
                pending: true
            }
        case FETCH_PRODUCTS_SUCCESS:
            return {
                ...state,
                pending: false,
                products: action.payload
            }
        case FETCH_PRODUCTS_ERROR:
            return {
                ...state,
                pending: false,
                error: action.error
            }
        default: 
            return state;
    }
}

export const getProducts = state => state.products;
export const getProductsPending = state => state.pending;
export const getProductsError = state => state.error;

Enter fullscreen mode Exit fullscreen mode

Okay, now we have a big part of the work done.

What's to note in the code above, are the three functions at the end of the reducer. Those are called selectors. Selectors are used to get defined parts of the state. In small applications they are overkill. But if you scale your app and it gets more and more complex, it gets really messy if you change something within your state. With selectors you need to change the selector and everything works fine.

I'll propably do a blog post about selectors, because I think they are really important to set up a scalable react/redux application.

Now where were we... Ah yes, big part of the work is done. The only thing left for us to do on the redux side is to write one of our fancy new actions.


// fetchProducts.js

import {fetchProductsPending, fetchProductsSuccess, fetchProductsError} from 'actions';

function fetchProducts() {
    return dispatch => {
        dispatch(fetchProductsPending());
        fetch('https://exampleapi.com/products')
        .then(res => res.json())
        .then(res => {
            if(res.error) {
                throw(res.error);
            }
            dispatch(fetchProductsSuccess(res.products);
            return res.products;
        })
        .catch(error => {
            dispatch(fetchProductsError(error));
        })
    }
}

export default fetchProducts;

Enter fullscreen mode Exit fullscreen mode

The action above is pretty simple. First we dispatch our pending action. Then we fetch the data from our API. We decode the json coming in into an object. Then we check for an error. If an error happend we throw it and call our error function. If everything went okay, we call the success action. The reducer handles the rest.

This is all about fetching data from a server...Nah, just kidding, it isn't. But this is how most posts about fetching data from an api end, right? But...

What about our application?

Oh, you want the products from your store to actually show in your react app? Okay okay, let's do this.

I assume you know how to connect your react app to your redux store using a provider. There are plenty of posts about this topic out there. After you've done that, you'll need a few components.

For me everything starts in a view. A view, for me, is a component which wraps up everything a user gets served into one parent component. This parent component has most of the connection to the redux store and shares the data with the components it encapsulates.


import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import fetchProductsAction from 'fetchProducts';
import {getProductsError, getProducts, getProductsPending} from 'reducer';

import LoadingSpinner from './SomeLoadingSpinner';
import ProductList from './ProductList';

class ProductView extends Component {
    constructor(props) {
        super(props);

        this.shouldComponentRender = this.shouldComponentRender.bind(this);
    }

    componentWillMount() {
        const {fetchProducts} = this.props;
        fetchProducts();
    }

    shouldComponentRender() {
        const {pending} = this.props;
        if(this.pending === false) return false;
        // more tests
        return true;
    }

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

        if(!this.shouldComponentRender()) return <LoadingSpinner />

        return (
            <div className='product-list-wrapper'>
                {error && <span className='product-list-error'>{error}</span>}
                <ProductList products={products} />
            </div>
        )
    }
}


const mapStateToProps = state => ({
    error: getProductsError(state),
    products: getProducts(state),
    pending: getProductsPending(state)
})

const mapDispatchToProps = dispatch => bindActionCreators({
    fetchProducts: fetchProductsAction
}, dispatch)

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(ProductView );

Enter fullscreen mode Exit fullscreen mode

So, a lot is going on here. We write a standard React component. Then we use connect to connect it to our redux store. Connect takes two parameters: One function mapStateToProps which maps parts of the state into your components props and one function mapDispatchToProps which maps functions into your props which are, when called, dispatched to redux.

Right at the end we put all those things together and voilá, we have a connection to our store.

In the mapStateToProps function we make use of our selectors we wrote earlier.

I like to add a function called shouldComponentRender to my view components and most of my components for that matter. I named it like this, because it's close to react's shouldComponentUpdate lifecycle method. It checks whether or not the component should render. If not, it renders a LoadingSpinner component.

I find it very beneficial to work like this, because the components are always reinitialized and all subcomponents are mounted again after the pending flag, which controls the rendering in this case, toggles. Therefore you can add redux state to a component's state in the constructor. (I don't want to talk about what goes into redux and what goes into component state, this is a topic for another post).

In most of my projects I found this one of the most annoying problems. Think of a component which renders a product. It is initialized by the data and then some subcomponents like a price calculator, which has a component state, is initialized in its constructor. When new data comes in, you need to do a check whether or not the calculator needs to reinitialize. With the shouldComponentRender function it's super easy to do so. Everytime the pending flag toggles (maybe because a new product is selected), everything reinitializes and is good to go.

Of course there are some reasons why you might have components within your view to not to rerender. If that's the case, just remove the shouldComponentRender function from your view and work with it within the subcomponents.

You can use some kind of fadeout/-in effect to improve the user experience.

Well, that's it. For real this time.

Thank you for reading my first blog post ever. I hope you enjoyed it, I hope you learned something and if you have any suggestions or tips for me to improve my react/redux skills or just want to say "hi", leave me some comments, I'd really like that.

Top comments (60)

Collapse
 
jimmymorris profile image
Jimmy Morris

This was a great article, I really appreciate the practical example, too many times it's not so.

One bit of feedback, I noticed in your fetchProducts.js file you have a typo:

dispatch(fetchProductsSuccess(res.products);

you're missing a closing parenthesis at the end of the dispatch

dispatch(fetchProductsSuccess(res.products));

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
markusclaus profile image
Markus Claus • Edited

Thank you very much for the comment Calum. :)

I did it a long time like you described and the solution was great. Then componentWillRecieveProps was marked as UNSAFE_componentWillReceiveProps in React 16.3. It will become deprecated in React 17 and the new getDerivedStateFromProps is here.

So I was wondering how I would solve this without depending to much on changes within the React lifecycle methodes and came up with my solution for the problem.

Putting the code into a function that sounded like a lifecycle method was my approach to keep the code readable and make everyone understand what is happening even if the code gets complicated. I keept it that way. :D

Also, I never got the idea why you would put state from redux into the react state if there is no reason to. I mean, you copy data from one state to another without doing anything with it. Seems like a waste of time. Can you enlighten me? Is there any benefit?

Collapse
 
shabbir profile image
Shabbir Haider • Edited

Even better :

let { requestPending } = this.state;

{ requestPending && <LoadingSpinner /> }
Collapse
 
jignesh1805 profile image
Jignesh1805

componentWillReceiveProps did'nt call.

Collapse
 
gabrielecimato profile image
Gabriele Cimato

Hey Markus! Congrats on your first blog post! Shameless plug, I built a middleware a while ago to help reduce boilerplate especially with async actions like the ones you mentioned in this article, you should check it out!

github.com/Gabri3l/redux-slim-async

Collapse
 
markusclaus profile image
Markus Claus

Hey Gabriele,

thanks for the tip. I know middlewares like yours. I used another one in a project recently. Was a real timesaver.

I will checkout yours for sure.

Collapse
 
gabrielecimato profile image
Gabriele Cimato

Any you would recommend to others out there ?

Thread Thread
 
markusclaus profile image
Markus Claus

Recommend? Not really. I used redux-promise and redux-action to achieve something like your middleware does.

In small applications it was great (as I think your middleware will be) but in larger scale applications there were cases where the thunks got complicated and I had to get rid of all the middlewares I found and do it from scratch (or with my own middleware in that case)

Thread Thread
 
gabrielecimato profile image
Gabriele Cimato

That's interesting! I used this for a medium/large codebase and we were fine so if you have a minute to bring up a complicated Case that made those middlewares unhelpful that would be great! I'm curious to see if there's a way I can adapt this one too not intricate use cases.

Thread Thread
 
shaka60hp profile image
Pablo San Juan

Hi Gabriele, I'm learning about your middleware here,
Do you use it INSTEAD of thunk?

Collapse
 
alegpereira profile image
Alejandro Pereira

Nice post. One question. You do this:

import fetchProductsAction from 'fetchProducts';

but i don't see fetchProductsAction in fetchProducts.js. I see only fetchProducts. Is this a typo or some code is missing?. I believe is only a mistake but just want to confirm.

Thanks.

Collapse
 
markusclaus profile image
Markus Claus • Edited

Thank you for the reply Alejandro.

You can import default exports with whatever name you want. I wanted to use fetchProducts in my code so I can't import it with this name because it would collide with the variable I use in a deeper scope.

To fix this I import everything that is a action with xxxAction and then use it without the Action suffix.

I hope this clarifies things.

Collapse
 
alegpereira profile image
Alejandro Pereira • Edited

You're right. I got confused. I'm learning react so i'm kind of new in those details and scanning the code properly.

Thanks!

Thread Thread
 
markusclaus profile image
Markus Claus

You are welcome mate. Good luck learning :)

Collapse
 
michaelcodes profile image
Michael Cowan

Hi, new to react so excuse my ignorance.
But why would you want to or need to save the api results in state for products?
Wouldn't it be better to just store them in a products list component or something similar? I'm not fully understanding why we would use redux for something like this.

Thanks for the help in advance!!! :D

Collapse
 
patmoore profile image
Patrick Moore

@michael -- because you want the product list to be a shared state.

For example, the product list is useful in a listing component - but it also might be useful in a checkout component that is validating that the product is still available.

Collapse
 
michaelcodes profile image
Michael Cowan

Okay thanks. I'm just trying to understand what you guys take into consideration doing things this way.

I've never worked with any form of state management before trying out react. So the differences are confusing to me compared to my previous vanilla js frontends.

Collapse
 
markusclaus profile image
Markus Claus

My article is more like an example how fetching data from an API works rather then a suggestion to do it like this in this exact case.

Deciding whether or not to put data in a store to share between components is totally up to your special usecase. Sometimes it makes sense, sometimes it doesn't.

For example, I work on a pretty big project right now and I decided to totally get rid of Redux. I only use the React Context API in the rare cases where I need to store data which is shared between components. For example in form handling.

Collapse
 
michaelcodes profile image
Michael Cowan

I see! I'm always open to suggestions and best practices though and you seem to know you're stuff.

Just wanted to understand the thought process.
I'll look up this context api next. Thanks!

Collapse
 
belyazidi56 profile image
Youssef Belyazidi

Hello Mr Markus Claus ,
Thank you for this great tutorial , I really appreciate, i see that you have a little error is : action.payload and you need to change it to : action.products

Thank you!

Collapse
 
rahko profile image
Rahim

Can anybody help me regarding this ?
See I have a redux store which is storing my state on home of home components
Then when I click on some link then it is again storing it into redux thats fine
But when I go back to home page
It should reuse the data from redux store
It should not call rest API again to fetch the data for homepage
It should use it from redux itself

I have used Java for rest API
Middleware thunk for async post

Collapse
 
capscode profile image
capscode

Thanks for this wonderful article.

i have 1 doubt, can someone please explain...

i understand that we have to use redux-thunk or saga to make async calls when we are making api calls inside the store and making an api call by dispatching an action.

but my question is..

  1. do we really need to make api calls by dispatching and action. Cant we simply call the fetchProducts function from component and pass the dispatch to it as argument.

inside the reducer, if we have code like below what's the issue/disadvantages?

function fetchProducts(dispatch) {
        dispatch(fetchProductsPending());
        fetch('https://exampleapi.com/products')
        .then(res => res.json())
        .then(res => {
            if(res.error) {
                throw(res.error);
            }
            dispatch(fetchProductsSuccess(res.products);
            return res.products;
        })
        .catch(error => {
            dispatch(fetchProductsError(error));
        })
    }
Enter fullscreen mode Exit fullscreen mode
  1. can we call api in useEffect or from component like below and update the store's state at 3 different stages of api call like below. Please tell me the disadvantage or issue with this approach.
        dispatch(fetchProductsPending());
        fetch('https://exampleapi.com/products')
        .then(res => res.json())
        .then(res => {
            if(res.error) {
                throw(res.error);
            }
            dispatch(fetchProductsSuccess(res.products);
            return res.products;
        })
        .catch(error => {
            dispatch(fetchProductsError(error));
        })
Enter fullscreen mode Exit fullscreen mode

Thanks a lot in advacne

Collapse
 
nguyenquangtin profile image
Tony Tin Nguyen

Hey Markus, thanks a lot for your guide. Great post!

Collapse
 
dera_johnson profile image
Johnson Chidera

Hi Marcus. I am trying to make an authentication using Google oauth with react, redux, redux-thunk and mongodb but my payload res.data is giving me an empty string even when I am logged in my mongo database. I have been stuck for days.

Collapse
 
tiruvengadam85 profile image
Tiruvengadam85

I have a component, I am passing brand values to the component as props, based on props value, components call different fecth API method in action fecthOMOrder, fecthSVOrder respectively. I need to specify same condition in connect, below condition is not working, please advise, thanks in advance.

if (this.props.brand=="OM")
{
export default connect(mapStateToProps,{fecthOMOrder})(OrderList);

}
else
{
export default connect(mapStateToProps,{fecthSVOrder })(OrderList);

}

Collapse
 
kaushalgautam profile image
Kaushal Gautam

Hi Markus!
Brilliant and insightful article. I will definitely refine my approach based on this. I have a query for you. Say, you have a form that takes a lot of fields as input. It also has some select dropdown fields as inputs which are interdependent. Eg: you select a country from a dropdown and then another dropdown is dynamically filled with the states of the selected country. These data will be fetched from firestore. How would you go about using redux firestore for storing these details and how would you design the actions/middleware functions?

Collapse
 
apekearo profile image
Anthony Pekearo • Edited

Hello Markus, I'm getting fetchProducts() is not a function in your componentWillMount function. And I don't see where it is defined. ComponentWillMount is being depricated, but don't think that is why i'm getting this error. Thank you
componentWillMount() {
const {fetchProducts} = this.props;
fetchProducts();
}

Collapse
 
mukhtorov profile image
Sardor

yeah, it is giving error

Collapse
 
nanettecodes profile image
Nanette

Does anyone have any advice on something.... Im working on an app that is going to make an API call using this technique. It will return a an array of items. each item will have a URL property. When the user clicks on said item (will be displayed in cards) I need a second API call to happen to that url that came from the first call... I cant figure out how to pass that data to the api call. any advice?

Collapse
 
abhi4pr profile image
abhi4pr

you didn't provide ProductList, i am trying

    {products.map(product => (
  • {product.name}
  • ))}

I am getting -> Cannot read property 'map' of undefined