3

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.

3
  • Why not putting the { type: FETCH_DATA } on a then? Commented Aug 19, 2017 at 16:15
  • @SamRad Thought about that too, I end up getting Actions must be plain objects. Use custom middleware for async actions. Commented Aug 19, 2017 at 16:19
  • That's true. You can't have async actions unless you use redux-thunk or any other ceremonial crap. A simple way is to write your API call separate and call your actions creators on then and catch. Does that make sense? Commented Aug 19, 2017 at 16:21

1 Answer 1

1

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;
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

This works, but I don't want the profile that already exists on this.props.profile to be restored to null if an error occurs, I just want to hold on to the existing profile and flash an error on the screen. How do I keep profile as profile in these circumstances with this approach?
Yeah i removed your ...state because there were only two properties in the state anyway. But of course you can tweak the reducer according to your needs. My main point was having the 3 actions instead of 2. Btw, you can even add a flag like isFetchingData to the state to display things like a loading indicator to the user.
Great advice, thank you for your time! Starting to get the hang of this now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.