Reactjs 在React中使用Redux和Redux Thunk处理失败的Ajax调用

Reactjs 在React中使用Redux和Redux Thunk处理失败的Ajax调用,reactjs,redux,react-redux,Reactjs,Redux,React Redux,对于我的React组件,我使用Redux(React-Redux)和Redux-Thunk来维护应用程序状态 有一个ShowGroup组件从后端检索group。如果组不存在(或在任何其他错误情况下),将调用错误回调来更新状态 class ShowGroup extends Component { constructor(props) { super(props); this.groupId = this.props.match.params.id; this.stat

对于我的React组件,我使用Redux(React-Redux)和Redux-Thunk来维护应用程序状态

有一个
ShowGroup
组件从后端检索
group
。如果组不存在(或在任何其他错误情况下),将调用错误回调来更新状态

class ShowGroup extends Component {
  constructor(props) {
    super(props);
    this.groupId = this.props.match.params.id;
    this.state = {
      'loaded': false,
      'error': null
    };
  }

  _fetchGroupErrorCallback = response => {
    this.setState({loaded: true, error: response});
  }

  componentDidMount() {
    this.props.fetchGroup(this.groupId, this._fetchGroupErrorCallback);
  }

  render() {
    //what is the best practice here ?
    let group = this.props.groups[this.groupId];
    if (!group) {
      if (!this.state.loaded) {
        return <div>Loading group ....</div>;
      }
      if (this.state.error) {
        return <div>{this.state.error.data.message}</div>;
      }
    }
    return (
      <div className="show-group">
        <form>
          {_.map(FIELDS, renderField.bind(this))}
        </form>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return { groups: state.groups };
}

const mapDispatchToProps = (dispatch) => {
    return {
        fetchGroup: (id, callback) => dispatch(fetchGroup(id, callback)),
        updateGroup: (id) => dispatch(updateGroup(id))
    };
};

export default reduxForm({
  validate,
  //a unique id for this form
  form:'ShowGroup',
  fields: _.keys(FIELDS),
  fields_def: FIELDS
})(
  connect(mapStateToProps, mapDispatchToProps)(ShowGroup)
);
groupFetchedErrored
操作创建者:

export function groupFetchErrored(response) {
  //handle the error here
  return {
     type: FETCH_GROUP_ERROR,
     response
  }
}
和减速器:

export default function(state=INITIAL_STATE, action) {
  switch(action.type) {
    //....
    case FETCH_GROUP_ERROR:
      return _.omit(state, action.response.data.id);
    default:
      return state;
  }
}
const rootReducer = combineReducers({
  form: formReducer,
  groups: groupReducer
});

export default rootReducer;
问题:

A.如果出现错误响应,组件将渲染三次: 1.第一次加载时(在进行ajax调用之后) 2. <代码>\u fetchGroupErrorCallback被调用,并且状态被设置,这将导致渲染 3.
groupFetchErrored
被调度,导致另一个渲染

B.如果响应成功,组件会被渲染两次,但状态不正确,因为没有任何东西会更新它(我认为为它添加回调是不正确的)

在使用React的Redux和Redux Thunk时,处理ajax错误响应的最佳实践是什么?action creator和reducer应该如何通知组件有问题

更新1

这是我的根减速机:

export default function(state=INITIAL_STATE, action) {
  switch(action.type) {
    //....
    case FETCH_GROUP_ERROR:
      return _.omit(state, action.response.data.id);
    default:
      return state;
  }
}
const rootReducer = combineReducers({
  form: formReducer,
  groups: groupReducer
});

export default rootReducer;
如您所见,我有
,这是一个形式对象:

{
   groupId1: groupData1,
   groupId2, groupData2, 
   ....
}

所以,另一个问题是,我应该如何指定一个组正在加载(我不能在
级别上进行
isLoading
errorFetching
,为每个无效的组id用户添加一个条目似乎不是一个好主意。或者可能有一种方法可以将无效的组id放入映射中,然后将其清除?不过,我不知道应该在哪里发生这种情况。

将所有状态放入与提取相关的数据进入Redux存储。然后该存储成为您唯一的真相来源

然后可以完全省略
fetchErrorCallback

您的减速器中已经有一个位置将收到错误通知:

case FETCH_GROUP_ERROR:
  return _.omit(state, action.response.data.id);
您可以将错误存储在redux状态:

case FETCH_GROUP_ERROR:
  const stateWithoutId = _.omit(state, action.response.data.id);
  return {
    ...stateWithoutId,
    errorFetching: action.response.error,
    isLoading: false
  }
然后通过
mapstatetops
errorFetching
isLoading
传递到组件中,就像您已经对
组所做的那样

此外,为了正确跟踪
isLoading
,最好在开始提取时(就在调用
axios.get
之前)分派另一个操作,可能
fetch\u组\u已启动
。然后在调度该操作类型时,在减速机中将
isLoading
设置为
true

case GROUP_FETCHED: 
  return { 
    ...state, 
    isLoading: false,
    group: action.response.data // or however you're doing this now.

  };
case FETCH_GROUP_STARTED:
  return { ...state, isLoading: true }; // or maybe isFetchingGroups is a better name
如果这是我的代码,我会做另一个更改:我不会将
响应
传递给动作创建者,而是将相关数据剥离出来并传递给他们。因此,reducer不需要知道如何处理
响应
对象;它只接收显示组件所需的数据正确。这样,所有处理axios调用并必须知道它们返回的对象的形状的代码都在一个地方—操作函数。类似于:

动作创造者 行动 减速器 您可能已经注意到我在上面的action creators中使用了
action.payload

通常的做法是在分派的操作上始终使用相同的名称作为数据属性(就像始终在整个应用程序的每个reducer和每个action creator中一样)。这样,reducer就不需要与action creator紧密耦合;它知道数据总是在
有效负载
字段中。(也许这就是您使用
响应所做的,但它似乎专门用于http响应。)


将与获取相关的所有状态数据放入Redux存储。然后该存储成为您的唯一真相来源

然后可以完全省略
fetchErrorCallback

您的减速器中已经有一个位置将收到错误通知:

case FETCH_GROUP_ERROR:
  return _.omit(state, action.response.data.id);
您可以将错误存储在redux状态:

case FETCH_GROUP_ERROR:
  const stateWithoutId = _.omit(state, action.response.data.id);
  return {
    ...stateWithoutId,
    errorFetching: action.response.error,
    isLoading: false
  }
然后通过
mapstatetops
errorFetching
isLoading
传递到组件中,就像您已经对
组所做的那样

此外,为了正确跟踪
isLoading
,最好在开始提取时(就在调用
axios.get
之前)分派另一个操作,可能
fetch\u组\u已启动
。然后在调度该操作类型时,在减速机中将
isLoading
设置为
true

case GROUP_FETCHED: 
  return { 
    ...state, 
    isLoading: false,
    group: action.response.data // or however you're doing this now.

  };
case FETCH_GROUP_STARTED:
  return { ...state, isLoading: true }; // or maybe isFetchingGroups is a better name
如果这是我的代码,我会做另一个更改:我不会将
响应
传递给动作创建者,而是将相关数据剥离出来并传递给他们。因此,reducer不需要知道如何处理
响应
对象;它只接收显示组件所需的数据正确。这样,所有处理axios调用并必须知道它们返回的对象的形状的代码都在一个地方—操作函数。类似于:

动作创造者 行动 减速器 您可能已经注意到我在上面的action creators中使用了
action.payload

通常的做法是在分派的操作上始终使用相同的名称作为数据属性(就像始终在整个应用程序的每个reducer和每个action creator中一样)。这样,reducer就不需要与action creator紧密耦合;它知道数据总是在
有效负载
字段中。(也许这就是您使用
响应所做的,但它似乎专门用于http响应。)


为什么要同时使用状态和道具?让道具成为唯一的真相来源,不要使用状态。这听起来不错,但是
fetchGroup
应该如何告诉组件服务器响应了错误?为什么要同时使用状态和道具?让道具成为唯一的真相来源,不要使用状态