【问题标题】:React and Redux: Uncaught Error: A state mutation was detected between dispatchesReact 和 Redux:未捕获的错误:在调度之间检测到状态突变
【发布时间】:2017-04-24 09:27:28
【问题描述】:

我正在使用“受控”组件(在组件中使用setState()),并在尝试保存表单数据时间歇性地收到此错误。 UserFormonSave 回调下面组件代码中的saveUser 方法。

我已经查看了有关此的 Redux 文档,但无法完全理解我在哪里修改状态以导致标题中的错误,具体来说是:Uncaught Error: A state mutation was detected between dispatches, in the path 'users.2'. This may cause incorrect behavior.

据我所知,只进行了局部修改,reducer 返回了全局状态的副本,并添加了我的更改。我一定是错过了什么,但是什么?

这是组件代码:

import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as userActions from '../../actions/userActions';
import UserForm from './UserForm';


export class ManageUserPage extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.state = {
      user:  Object.assign({}, this.props.user),
      errors:  {},
      saving:  false
    };
    this.updateUserState = this.updateUserState.bind(this);
    this.saveUser = this.saveUser.bind(this);
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.user.id != nextProps.user.id) {
      this.setState(
        {
          user:  Object.assign({}, nextProps.user)
        }
      );
    }
  }

  updateUserState(event) {
    const field = event.target.name;
    let user = Object.assign({}, this.state.user);
    user[field] = event.target.value;
    return this.setState({user: user});
  }

  userFormIsValid() {
    let formIsValid = true;
    let errors = {};

    if (this.state.user.firstName.length < 3) {
      errors.firstName = 'Name must be at least 3 characters.';
      formIsValid = false;
    }
    this.setState({errors: errors});
    return formIsValid;
  }


  saveUser(event) {
    event.preventDefault();
    if (!this.userFormIsValid()) {
      return;
    }
    this.setState({saving: true});
    this.props.actions
      .saveUser(this.state.user)
      .then(() => this.redirect())
      .catch((error) => {
        this.setState({saving: false});
      });
  }

  redirect() {
    this.setState({saving: false});
    this.context.router.push('/users');
  }

  render() {
    return (
      <UserForm
        onChange={this.updateUserState}
        onSave={this.saveUser}
        errors={this.state.errors}
        user={this.state.user}
        saving={this.state.saving}/>
    );
  }
}

ManageUserPage.propTypes = {
  user:  PropTypes.object.isRequired,
  actions: PropTypes.object.isRequired
};

ManageUserPage.contextTypes = {
  router: PropTypes.object
};


function getUserById(users, userId) {
  const user = users.find(u => u.id === userId);
  return user || null;
}

function mapStateToProps(state, ownProps) {
  let user = {
    id:        '',
    firstName: '',
    lastName:  ''
  };

  const userId = ownProps.params.id;

  if (state.users.length && userId) {
    user = getUserById(state.users, userId);
  }


  return {
    user:  user
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(userActions, dispatch)
  };
}

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

这是减速器:

import * as types from '../actions/actionTypes';
import initialState from './initialState';


export default(state = initialState.users, action) =>
{
  switch (action.type) {
    case types.CREATE_USER_SUCCESS:
      return [
        // grab our state, then add our new user in
        ...state,
        Object.assign({}, action.user)
      ];

    case types.UPDATE_USER_SUCCESS:
      return [
        // filter out THIS user from our copy of the state, then add our updated user in
        ...state.filter(user => user.id !== action.user.id),
        Object.assign({}, action.user)
      ];

    default:
      return state;
  }
};

以下是操作:

import * as types from './actionTypes';
import userApi from '../api/mockUserApi';
import {beginAjaxCall, ajaxCallError} from './ajaxStatusActions';


export function createUserSuccess(user) {
  return {type: types.CREATE_USER_SUCCESS, user};
}

export function updateUserSuccess(user) {
  return {type: types.UPDATE_USER_SUCCESS, user};
}

export function saveUser(user) {
  return function (dispatch, getState) {
    dispatch(beginAjaxCall());
    return userApi.saveUser(user)
      .then(savedUser => {
        user.id
          ? dispatch(updateUserSuccess(savedUser))
          : dispatch(createUserSuccess(savedUser));
      }).catch(error => {
        dispatch(ajaxCallError(error));
        throw(error);
      });
  };
}

这是模拟 API 层:

import delay from './delay';

const users = [
  {
    id: 'john-smith',
    firstName: 'John',
    lastName: 'Smith'
  }
];

const generateId = (user) => {
  return user.firstName.toLowerCase() + '-' + user.lastName.toLowerCase();
};

class UserApi {
  static saveUser(user) {
    user = Object.assign({}, user); 
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const minUserNameLength = 3;
        if (user.firstName.length < minUserNameLength) {
          reject(`First Name must be at least ${minUserNameLength} characters.`);
        }

        if (user.lastName.length < minUserNameLength) {
          reject(`Last Name must be at least ${minUserNameLength} characters.`);
        }

        if (user.id) {
          const existingUserIndex = users.findIndex(u => u.id == u.id);
          users.splice(existingUserIndex, 1, user);
        } else {
          user.id = generateId(user);
          users.push(user);
        }
        resolve(user);
      }, delay);
    });
  }
}

export default UserApi;

【问题讨论】:

  • 在mapStateToProps中,你为什么要返回用户和用户?
  • @HusseinAlkaf 你是对的,这不是必需的。我已经从另一个重用了这个组件。当然,它不会影响错误,但已编辑。谢谢!
  • 对于未来的寻求者:在我的情况下,当我尝试改变从 state 中获取的值时发生了这个错误。

标签: reactjs redux


【解决方案1】:

@DDS 为我指明了正确的方向(谢谢!),因为是其他地方的突变导致了问题。

ManageUserPage 是 DOM 中的顶级组件,但另一个名为 UsersPage 的路由上的不同组件在其渲染方法中正在改变状态。

最初的渲染方法是这样的:

render() {
    const users = this.props.users.sort(alphaSort);
    return (
      <div>
        <h1>Users</h1>
        <input type="submit"
               value="Add User"
               className="btn btn-primary"
               onClick={this.redirectToAddUserPage}/>
        <UserList
          users={users}/>
      </div>
    );
}

我将users 分配更改为以下内容,问题已解决:

const users = [...this.props.users].sort(alphaSort);

【讨论】:

  • 非常感谢... 9 小时的长时间调试,我知道这是问题所在。再次感谢队友:)
【解决方案2】:

问题不在于这个组件或减速器。它可能在父组件中,您可能在某处分配了users[ix] = savedUser,并且用户数组最终与状态中的数组相同。

【讨论】:

    【解决方案3】:

    通过遵循redux official docs 上建议的任何更新模式解决了状态突变问题

    我更喜欢在使用 immer.produce 的 reducer 中使用 createReducer

    import { createReducer } from 'redux-starter-kit'
    
    const initialState = {
      first: {
        second: {
          id1: { fourth: 'a' },
          id2: { fourth: 'b' }
        }
      }
    }
    
    const reducer = createReducer(initialState, {
      UPDATE_ITEM: (state, action) => {
        state.first.second[action.someId].fourth = action.someValue
      }
    })
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-25
      • 1970-01-01
      • 2017-07-17
      • 2019-10-13
      • 2017-11-04
      • 1970-01-01
      • 2016-12-08
      • 2017-10-15
      相关资源
      最近更新 更多