Well I am doing a React-Readux application, and I have the following Array of objects that my store has:
const authors = [
{
id: 'cory-house',
firstName: 'Cory',
lastName: 'House',
address: {
city: 'Caracas'
}
},
{
id: 'scott-allen',
firstName: 'Scott',
lastName: 'Allen',
address: {
city: 'Caracas'
}
},
{
id: 'dan-wahlin',
firstName: 'Dan',
lastName: 'Wahlin',
address: {
city: 'Caracas'
}
}
];
When I do an update, in my reducer, I have the following:
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function authorReducer(state = initialState.authors, action) {
console.log('actions ', action);
console.log('state ', state);
switch (action.type) {
case types.LOAD_AUTHORS_SUCCESS:
return action.authors;
case types.CREATE_AUTHOR_SUCCESS:
return [
...state,
Object.assign({}, action.author)
];
case types.UPDATE_AUTHOR_SUCCESS:
return [
...state.filter(author => author.id !== action.author.id),
Object.assign({}, action.author)
];
case types.DELETE_AUTHOR_SUCCESS:
var existingAuthorIndex = state.findIndex(a => a.id == action.author.id);
console.log('Index ', existingAuthorIndex);
return [
...state.filter(author => author.id !== action.author.id)
]
default:
return state;
}
}
The Object.assign that I have in UPDATE_AUTHOR_SUCCESS
only makes a superficial "copy", which means that it only copies a level of my Object, so when trying to modify the address field, it throws me the following error:
browser.js? 9520: 40 Uncaught Error: A state mutation was detected between dispatches, in the path
authors.0.address.city
. This may cause incorrect behavior.
The whole problem is because I have to make a deep copy (Deep-copy) of this but I do not know how to do it.
I tried to do it:
case types.UPDATE_USERS:
console.log('My uaser ', action);
return [
...state.filter(user => user.id !== action.user.id),
Object.assign({}, action.user, action.user.address)
];
But it still fails me, since it is not the right thing to do.
Update
For some reason in the component where I manage my update I had it like this:
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import AuthorForm from './AuthorForm';
import * as authorActions from '../../actions/authorActions';
import toastr from 'toastr';
export class AuthorManagePage extends React.Component{
constructor(props, context){
super(props, context);
this.state={
author: Object.assign({}, props.author),
errors:{},
saving: false
};
this.saveAuthor = this.saveAuthor.bind(this);
this.updateAuthorState = this.updateAuthorState.bind(this);
}
componentWillReceiveProps(nextProps) {
if (this.props.author.id != nextProps.author.id) {
// Necessary to populate form when existing course is loaded directly.
var clone = Object.assign({}, nextProps.author)
clone.address = Object.assign({}, nextProps.author.address);
this.setState({authors: clone});
}
}
updateAuthorState(event) {
// const field = event.target.name;
// let author = this.state.author;
// author[field] = event.target.value;
// return this.setState({author: author});
const hasSubField = event.target.name.split('.');
console.log(hasSubField)
if (hasSubField.length > 1) {
let author = this.state.author;
author[hasSubField[0]][hasSubField[1]] = event.target.value;
return this.setState({author:author});
}
// else if(hasSubField.length > 2){
// // let user = this.state.user;
// // user.address.geo.lat = event.target.value;
// // return this.setState({user:user});
//
//
// let user = Object.assign({}, this.state.user);
// user.address.geo.lat = event.target.value;
// return this.setState({user:user});
//
// }
else {
const field = event.target.name;
let author = this.state.user;
author[field] = event.target.value;
return this.setState({author:author });
}
}
redirect() {
this.setState({saving: false});
toastr.success('Author saved');
this.context.router.push('/authors');
}
saveAuthor(event){
event.preventDefault();
this.setState({saving: true});
var author = this.state.author;
this.props.actions.saveAuthor(this.state.author).then(() => this.redirect())
.catch(error => {
toastr.error(error);
this.setState({saving: false});
});
}
render(){
return(
<AuthorForm
author={this.state.author}
onSave={this.saveAuthor}
errors={this.state.errors}
saving={this.state.saving}
onChange={this.updateAuthorState}
/>
);
}
}
function getAuthor(authors, authorId){
const author = authors.filter(author => author.id === authorId);
if(author.length) return author[0];
return null;
}
AuthorManagePage.propTypes = {
author: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
};
//Pull in the React Router context so router is available on this.context.router.
AuthorManagePage.contextTypes = {
router: PropTypes.object
};
function mapStateToProps(state, ownProps) {
let authorId = ownProps.params.id;
let author = {id: '', firstName: '', lastName: '', address: { city: ''}};
if (authorId && state.authors.length > 0) {
author = getAuthor(state.authors, authorId);
}
return {
author: author
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(authorActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AuthorManagePage);
And I realized that in the constructor, in the this.state
the object was not being copied completely, I only changed it for this:
this.state={
author: JSON.parse(JSON.stringify(props.author)),
errors:{},
saving: false
};
And it works for me, but what I do not understand is why?