Reputation: 2645
I have an object that holds a company and inside a company, there is an array of teams.
company: {
teams: [
{
name: 'test team',
description: 'dddd',
team_manager: null,
company: '592577d5b591966c8e535865',
permalink: 'test-team',
createdAt: '2017-05-30T07:38:58.983Z',
updatedAt: '2017-05-30T07:38:58.983Z',
id: '592d219277923054118e7299'
}
],
name: 'test company2',
createdAt: '2017-05-24T12:08:53.418Z',
updatedAt: '2017-05-24T12:08:53.419Z',
id: '592577d5b591966c8e535865'
}
}
When a team is added, I am using this reducer to push the team to the array.
case types.ADD_TEAM_SUCCESS :
return Object.assign({}, state, state.teams.push(action.newTeam));
This works fine, however, in the console I am getting a warning that says:
'Error: A state mutation was detected inside a dispatch, in the path: company.teams.0
'
What is the correct way to close the object and push the new team to the array?
Upvotes: 2
Views: 629
Reputation: 987
As describe before Object.assign() does not do a deep clone of your object.
My suggestion is normalize your store and split into two stores Company and Teams.
Than use combineReducers and connect this into one store you will simplify reducers and will make code more readable. Also you can than play with performance.
I mean ... if you will have component just working with company and you will have change in teams you will avoid easily re-render.
Check Dan Abramov grate video tutorial Redux: Normalizing the State Shape
I will do that somehow (sorry for code in typescript)
export interface ICompanyItem {
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
}
export interface ITeamItem {
id: string;
companyId: string
name: string;
description: string;
team_manager: string;
createdAt: Date;
updatedAt: Date;
}
export interface ICompaniesState {
items: { [id: string]: ICompanyItem };
}
export interface ITeamsState {
items: { [id: string]: ITeamItem };
}
export const teamsReducer: Reducer<ITeamsState> = (state: ITeamsState, action: Action) => {
if (isActionType(action, AddNewTeamItem)) {
let itemsClone: { [id: string]: ITeamItem } = { ...state.items, [action.id]: action.team };
return {
...state,
items: itemsClone
};
}
return state || unloadedState;
};
Upvotes: 0
Reputation: 1365
If you are using es2015, I you can do something like:
return Object.assign({}, state, { teams: [...state.teams, action.newTeam] });
You are pushing an object directly to state.teams
. You should make a copy first and use that instead. If you can't use the spread operator, you can slice state.teams into a new variable and push to that instead.
Upvotes: 3
Reputation: 29739
The problem is that Object.assign()
does not do a deep clone of your object [1] (see "Warning for Deep Clone").
If you don't have any utility libraries in your stack (like lodash) you can use JSON encoding and decoding to get the job done:
case types.ADD_TEAM_SUCCESS:
let stateCopy = JSON.parse(JSON.stringify(state));
state.teams.push(action.newTeam);
return state;
[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Upvotes: 1