redux-thunk:错误:操作必须是纯对象。使用自定义中间件进行异步操作

时间:2018-09-28 23:55:53

标签: reactjs redux-thunk

尝试使用API​​创建简单的CRUD应用。我已经可以使用登录和注册了,所以我知道在商店中正确设置了redux-thunk。

我已经看到很多类似的问题,但是没有一个答案可以解决我的问题。我知道调度应该返回函数。我很确定是这样。我是新来的反应者,对如何调试有些迷茫。 Console.log没有显示任何内容,因此我使用了警报。很高兴提供任何其他信息。谢谢。

错误

Error: Actions must be plain objects. Use custom middleware for async actions.
  20 |  componentDidMount() {
  21 |    const { dispatch } = this.props;
  22 |    const { error, rosterMembers, isFetching } = this.props;
  23 |    dispatch(rosterActions.getAll());
  24 | 
  25 |  }
  26 | 

容器:RosterListCard.js

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

import {Card, CardBody, Col, ButtonToolbar} from 'reactstrap';
import EmailIcon from 'mdi-react/EmailIcon'
import CheckboxMarkedCircleIcon from 'mdi-react/CheckboxMarkedCircleIcon'
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
import {Link} from 'react-router-dom';
import { Table } from 'reactstrap';
import { rosterActions } from '../../../../redux/actions';

class RosterListCard extends PureComponent {
  constructor(props) {
    super(props);
//    this.getNoRowsRenderer = this.getNoRowsRenderer.bind(this);
//    this.getRowClassName = this.getRowClassName.bind(this);
  }

  componentDidMount() {
    const { dispatch } = this.props;
    const { error, rosterMembers, isFetching } = this.props;
    dispatch(rosterActions.getAll());

  }

  componentWillReceiveProps(nextProps) {
    const { dispatch } = nextProps;
    dispatch(rosterActions.getAll());
  }

  getNoRowsRenderer() {
    return (
      <div className="noRows">
        No rows
      </div>
    );
  }

  render() {
    const { error, rosterMembers, isFetching } = this.props;
    return (
      <div className="container">
        {console.log(error)}
        {console.log(isFetching)}
        {console.log(rosterMembers)}
        {error &&
          <div className="alert alert-danger">
            {error.message || "Unknown errors."}
          </div>}
        {!isFetching &&
          rosterMembers.length === 0 &&
          <div className="alert alert-warning">Oops, nothing to show.</div>}

        {rosterMembers &&

          <Table striped>
            <thead>
              <tr>
                <th>Name</th>
                <th>Title</th>
              </tr>
            </thead>
            <tbody>
              {this.props.rosterMembers.map((rosterMember) => (
                <tr>
                  <th scope="row">{rosterMember.fullName}</th>
                  <td>{rosterMembers.title}</td>
                </tr>
              ))}
            </tbody>
          </Table>
        }
      </div>
    )
  }
}

RosterListCard.propTypes = {
  rosterMembers: PropTypes.array.isRequired,
  isFetching: PropTypes.bool.isRequired,
  error: PropTypes.object,
  dispatch: PropTypes.func.isRequired
};

function mapStateToProps (state) {

  return {
    error: null,
    isFetching: false,
    didInvalidate: false,
    totalCount: 0,
    rosterMembers: []
  };
}

export default connect(mapStateToProps)(RosterListCard);

动作:roster.actions.js

import { sessionService } from 'redux-react-session';
import { rosterConstants } from '../constants';
import { callApi } from "../../utilities/api.utility.js";

export const rosterActions = {
  getAll
};

function rosterRequest() {
  return {
    type: rosterConstants.GETALL_REQUEST
  };
}


function rosterSuccess() {
  return function(payload) {
    return {
      type: rosterConstants.GETALL_SUCCESS,
      rosterMembers: payload.items,
      totalCount: payload.total_count
    };
  };
}

function rosterFailure() {
  return function(error) {
    return {
      type: rosterConstants.GETALL_FAILURE,
      error
    };
  };
}

export function getAll() {
  sessionService.loadSession()
    .then(session => {
//      if (typeof session.token === 'undefined') return rosterFailure();
      const url = `${process.env.REACT_APP_API_BASE_URL}/Rosters?access_token=${session.token}`;
      return callApi(
        url,
        null,
        rosterRequest(),
        rosterSuccess(),
        rosterFailure()
      )
    });
}

API模块:api.utility.js

import "isomorphic-fetch";

export function checkStatus(response) {
  if (!response.ok) {
    // (response.status < 200 || response.status > 300)
    const error = new Error(response.statusText);
    error.response = response;
    throw error;
  }
  return response;
}

export function parseJSON(response) {
  return response.json();
}

/**
 * A utility to call a restful service.
 *
 * @param url The restful service end point.
 * @param config The config object of the call. Can be null.
 * @param request The request action.
 * @param onRequestSuccess The callback function to create request success action.
 *                 The function expects response json payload as its argument.
 * @param onRequestFailure The callback function to create request failure action.
 *                 The function expects error as its argument.
 */
export function callApi(
  url,
  config,
  request,
  onRequestSuccess,
  onRequestFailure
) {
  alert('request');
  return dispatch => {
    alert('request***');
    dispatch(request);
    return fetch(url, config)
      .then(checkStatus)
      .then(parseJSON)
      .then(json => {
        alert('request***');
        dispatch(onRequestSuccess(json));
      })
      .catch(error => {
        alert('request***');
        const response = error.response;
        if (response === undefined) {
          dispatch(onRequestFailure(error));
        } else {
          error.status = response.status;
          error.statusText = response.statusText;
          response.text().then(text => {
            try {
              const json = JSON.parse(text);
              error.message = json.message;
            } catch (ex) {
              error.message = text;
            }
            dispatch(onRequestFailure(error));
          });
        }
      });
  };
  alert('request end');
}

1 个答案:

答案 0 :(得分:0)

如果您使用的是redux-thunk,那么您的异步“动作创建者”应该类似于:

export function getAll() {
  return dispatch => {
      sessionService.loadSession()
          .then(session => {
              if (typeof session.token === 'undefined') return dispatch(rosterFailure());
              const url = `${process.env.REACT_APP_API_BASE_URL}/Rosters?access_token=${session.token}`;
              dispatch(rosterRequest());
              fetch(url, config).then(response => {
                  // validate response and convert to JSON
                  dispatch(rosterSuccess(json));
              }
           }).catch(err => {
              // Handle/Transform error
              dispatch(rosterFailure(err));
           }
         });
  }
}

此外,我认为您缺少有关redux-thunk的一些详细信息。异步dispatch其他动作的动作应返回函数(其中dispatch是第一个也是唯一的必需参数)。其他动作应该只返回带有type的典型动作对象以及化简函数更新其存储所需要的其他信息。

例如,您的rosterRequest看起来完全符合我的期望,但是rosterSuccess应该看起来像这样:

function rosterSuccess(paylod) {
  return {
    type: rosterConstants.GETALL_SUCCESS,
    rosterMembers: payload.items,
    totalCount: payload.total_count
  };
}

然后,您在mapDispatchToProps组件时可以connect,而不希望组件直接具有dispatch道具。

例如,在RosterListCard.js的底部:

RosterListCard.propTypes = {
  rosterMembers: PropTypes.array.isRequired,
  isFetching: PropTypes.bool.isRequired,
  error: PropTypes.object,
  getAllRoster: PropTypes.func.isRequired
};

function mapStateToProps (state) {

  return {
    error: null,
    isFetching: false,
    didInvalidate: false,
    totalCount: 0,
    rosterMembers: []
  };
}

function mapDispatchToProps(dispatch) {
    return {
        getAllRoster: rosterActions.getAll
    }
}

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

只要需要,您就可以从this.props调用该函数:

componentDidMount() {
    const { error, rosterMembers, isFetching } = this.props;
    this.props.getAllRoster();
}