我正在使用“受控”组件(在组件中使用setState()
),在尝试保存表单数据时间歇性地出现此错误。UserForm
onSave
在下面的组件代码中回调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;
4条答案
按热度按时间sd2nnvve1#
@DDS为我指出了正确的方向(谢谢!),因为是其他地方的突变导致了这个问题。
ManageUserPage
是DOM中的顶级组件,但另一个路径上的另一个组件UsersPage
正在其呈现方法中改变状态。最初的render方法如下所示:
我将
users
赋值更改为以下值,问题已解决:eivgtgni2#
问题不在这个组件或reducer中,可能在父组件中,在父组件中,您可能在某个地方分配了
users[ix] = savedUser
,而users数组最终与state中的数组相同。vngu2lb83#
通过遵循redux official docs上建议的任何更新模式,解决了状态突变问题
我更喜欢在使用immer的reducer中使用
createReducer
。后台生成:vuv7lop34#
可变操作包括push、pop、splice、shift、unshift、reverse和sort。但是'the state'是不可变的,所以你要么需要为它们找到可替换的函数(例如,使用'concat()'而不是'push()'),要么在spread数组的副本上使用它们,例如:
(您通过[... arr]复制用户,因此原始的“users-state”没有被改变)