James Ives James Ives - 30 days ago 15
React JSX Question

Adding an error state with Axios and Redux

I'm having a bit of trouble getting my application to work how I'd like it. I'm fairly new to React and Redux so bear with me.

Right now I can call

this.props.fetchData('usernamehere')
to fetch some data about a user, when it's successful it updates
this.props.profile
which I use to update the React component. The problem with this is handling errors.

I've tried to handle this by adding a
FETCH_DATA_ERROR
reducer, but the problem I'm having is that when it 404's it still replaces
this.props.profile
with an empty object instead of just updating
this.props.error
. Ideally I want to keep the current profile until it successfully finds a new one, and then show the user what the error was with the username they entered by updating
this.props.error
.

Right now I have the following action creator setup:

import axios from 'axios';
import { FETCH_DATA, FETCH_DATA_ERROR } from './types';

export const ROOT_URL = 'https://api.github.com/users'

export function fetchData(user) {
const request = axios.get(`${ROOT_URL}/${user}`)
.catch(function (error) {
return {
type: FETCH_DATA_ERROR,
payload: error
}
});

return {
type: FETCH_DATA,
payload: request
}
}


And reducer:

import { FETCH_DATA, FETCH_DATA_ERROR } from '../actions/types';

const INITIAL_STATE = { profile: null, error: null }

export default function(state = INITIAL_STATE, action) {
switch(action.type) {
case FETCH_DATA:
return { ...state, profile: action.payload.data };

case FETCH_DATA_ERROR:
return { ...state, error: action.payload };

default:
return state;
}
}


If anyone has any suggestions they would be greatly appreciated. I feel like I'm on the right path but can't seem to figure out where I'm going wrong.

Answer Source

So far you have an action that signals the beginning of the request (FETCH_DATA) and one that signals that the request failed (FETCH_DATA_ERROR). Typically this is modelled with a third one, that signals that the request resulted in a positive response (maybe FETCH_DATA_SUCCESS).

You would need to rewrite your action creator using something like https://github.com/gaearon/redux-thunk so that it first dispatches only FETCH_DATA, and then in the then/catch-handlers of axios.get you dispatch the success or failure actions:

import { FETCH_DATA, FETCH_DATA_SUCCESS, FETCH_DATA_ERROR } from '../actions/types';

// ASYNC ACTION CREATOR

export const fetchData = user => (dispatch) => {
    dispatch({
        type: FETCH_DATA
    });

    return axios.get(`${ROOT_URL}/${user}`)
        .then(response => dispatch({
            type: FETCH_DATA_SUCCESS,
            payload: response
        }))
        .catch(error => dispatch({
            type: FETCH_DATA_ERROR,
            payload: error
        }));
};


// REDUCER

const INITIAL_STATE = {
    profile: null,
    error: null
};

export default function(state = INITIAL_STATE, action) {
    switch(action.type) {
        // Start of request - discard old data and reset old errors.  
        case FETCH_DATA:
            return {
            // It's important to set all of these to properly model the request lifecycle
            // and avoid race conditions etc.
            profile: null,
            error: null
        };

        // End of request - save profile and signal that there was no error.
        case FETCH_DATA_SUCCESS:
            return {
                profile: action.payload.data,
                error: null
            };

        // End of request - discard old profile and save error, to display it to the user for example.
        case FETCH_DATA_ERROR:
            return {
                profile: null,
                error: action.payload
            };

        default:
            return state;
    }
}