redux-promise with Axios, and how do deal with err

2019-01-23 17:13发布

So, I see on an error, redux-promise hands me back error: true, along with the payload, but that is once it hits the reducer... to me, decoupling the request AND error condition is a bit odd, and seems inappropriate. What is an effective way to also deal with error condition when using axios w/ reduc-promise (middleware).. here is the gist of what i have..

in action/
const request = axios(SOME_URL);

return {
   type: GET_ME_STUFF,
   payload: request
}

in reducer/
  const startState = {
     whatever: [],
     error: false
  }

  case GET_ME_STUFF:
     return {...state, startState, {stuff:action.payload.data, error: action.error? true : false}}

etc... then I can deal with the error.. so, my api call is now split into two seperate areas and that seems wrong.... there must be something I am missing here. I would think in the /actions I can pass in a callback that handles a new action etc.. or something, but not split it.

6条回答
看我几分像从前
2楼-- · 2019-01-23 17:28

This might not be the best approach but it works for me. I pass the 'this' of my component as var context. Then when i get response back i just execute the methods defined in my components context. In my component i have successHdl and errorHdl. From there i can trigger more redux actions as normal. I checked all the previous answers and seem too daunting for such a trivial task.

export function updateJob(payload, context){

    const request = axios.put(UPDATE_SOMETHING, payload).then(function (response) {
        context.successHdl(response);
    })
    .catch(function (error) {
        context.errorHdl(error);
    });;

    return {
        type: UPDATE_SOMETHING,
        payload: payload,
    }
}
查看更多
手持菜刀,她持情操
3楼-- · 2019-01-23 17:35

It looks like you can catch the error where you make the dispatch, then make an separate error dispatch if it happens. It's a bit of a hack but it works.

  store.dispatch (function (dispatch) {
      dispatch ({
        type:'FOO',
        payload:axios.get(url)
      })
      .catch (function(err) {
        dispatch ({
          type:"FOO" + "_REJECTED",
          payload:err
        });
      });
  });

and in the reducer

const reducer = (state=initialState, action) => {
  switch (action.type) {
    case "FOO_PENDING": {
      return {...state, fetching: true};
    }
    case "FOO_REJECTED": {
      return {...state, fetching: false, error: action.payload};
    }
    case "FOO_FULFILLED": {
      return {
        ...state,
        fetching: false,
        fetched: true,
        data: action.payload,
      };
    }
  }
  return state;
};
查看更多
Animai°情兽
4楼-- · 2019-01-23 17:36

The accepted answer doesn't make use of redux-promise. Since the question is actually about handling errors using redux-promise I provide another answer.

In the reducer you should inspect the existence of the error attribute on the action object:

// This is the reducer
export default function(previousState = null, action) {
  if (action.error) {
    action.type = 'HANDLE_XHR_ERROR'; // change the type
  }  
  switch(action.type) {
    ...

And change the type of the action, triggering a state change for an error handling component that you have set up for this.

You can read a bit more about it here on github.

查看更多
来,给爷笑一个
5楼-- · 2019-01-23 17:40

I've had to go through a similar situation. The challenge is that you likely won't be able to evaluate the results of the promise until it is at the reducer. You could handle your exceptions there but it's not the best pattern. From what I've read reducers are meant only to return appropriate pieces of state based on action.type and do nothing else.

So, enter an additional middleware, redux-thunk. Instead of returning an object, it returns a function, and it can coexist with promise.

It's explained quite well at http://danmaz74.me/2015/08/19/from-flux-to-redux-async-actions-the-easy-way/ [archived here]. Essentially, you can evaluate the promise here and dispatch through the other action creators before the promise result hits the reducers.

In your actions file, add additional action creators that would handle the success and error (and any other) states.

function getStuffSuccess(response) {
  return {
    type: GET_ME_STUFF_SUCCESS,
    payload: response
  }
}

function getStuffError(err) {
  return {
    type: GET_ME_STUFF_ERROR,
    payload: err
  }
}

export function getStuff() {
  return function(dispatch) {
    axios.get(SOME_URL)
      .then((response) => {
        dispatch(getStuffSuccess(response))
      })
      .catch((err) => {
        dispatch(getStuffError(err))
      })
  }
}

return null

This is roughly to how you might translate your pseudocode to what is explained at the link. This handles evaluating the promise directly in your action creator and firing off the appropriate actions and payloads to your reducers which follows the convention of action -> reducer -> state -> component update cycle. I'm still pretty new to React/Redux myself but I hope this helps.

查看更多
戒情不戒烟
6楼-- · 2019-01-23 17:41

Don't use redux-promise. It overcomplicates something that's actually super simple to do yourself.

Instead read the redux docs: http://redux.js.org/docs/advanced/AsyncActions.html

It'll give you a much better understanding of how to handle this kind of interactions and you'll learn how to write something (better than) redux-promise yourself.

查看更多
Lonely孤独者°
7楼-- · 2019-01-23 17:45

Still using redux-promises you can do something like this which I think is an elegant way to deal with this problem.

First, set a property in the redux state that will hold any ajax errors that may occurred.

ajaxError: {},

Second, setup a reducer to handle ajax errors:

export default function ajaxErrorsReducer(state = initialState.ajaxError, action) {
  if (action.error) {
    const { response } = action.payload;
    return {
      status: response.status,
      statusText: response.statusText,
      message: response.data.message,
      stack: response.data.stack,
    };
  }
  return state;
}

Finally, create a very simple react component that will render errors if there are any (I am using the react-s-alert library to show nice alerts):

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

class AjaxErrorsHandler extends Component {

  constructor(props, context) {
    super(props, context);
    this.STATUS_GATE_WAY_TIMEOUT = 504;
    this.STATUS_SERVICE_UNAVAILABLE = 503;
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.ajaxError !== nextProps.ajaxError) {
      this.showErrors(nextProps.ajaxError);
    }
  }

  showErrors(ajaxError) {
    if (!ajaxError.status) {
      return;
    }
    Alert.error(this.getErrorComponent(ajaxError), {
      position: 'top-right',
      effect: 'jelly',
      timeout: 'none',
    });
  }

  getErrorComponent(ajaxError) {
    let customMessage;
    if (
      ajaxError.status === this.STATUS_GATE_WAY_TIMEOUT ||
      ajaxError.status === this.STATUS_SERVICE_UNAVAILABLE
    ) {
      customMessage = 'The server is unavailable. It will be restored very shortly';
    }
    return (
      <div>
        <h3>{ajaxError.statusText}</h3>
        <h5>{customMessage ? customMessage : ajaxError.message}</h5>
      </div>
    );
  }

  render() {
    return (
      <div />
    );
  }

}

AjaxErrorsHandler.defaultProps = {
  ajaxError: {},
};

AjaxErrorsHandler.propTypes = {
  ajaxError: PropTypes.object.isRequired,
};

function mapStateToProps(reduxState) {
  return {
    ajaxError: reduxState.ajaxError,
  };
}

export default connect(mapStateToProps, null)(AjaxErrorsHandler);

You can include this component in your App component.

查看更多
登录 后发表回答