Changing state in Redux
I am trying to add an element to an array in the state
and change another array element's property. Say we have the following state
structure:
{
menuItems: [{
href: '/',
active: true
}]
}
After dispatching the ADD_MENU_ITEM
action I want to end up with this state
:
{
menuItems: [{
href: '/new',
active: true
}, {
href: '/',
active: false,
}]
}
I have tried managing this in Redux reducers in several fashions:
function reducer(state = {}, action) {
switch (action.type) {
case ADD_MENU_ITEM: {
let menuItems = state.menuItems;
let newMenuItem = action.newMenuItem;
// First try
menuItems[0].active = false;
menuItems.unshift(newMenuItem);
state = Object.assign({}, state, { menuItems: menuItems });
// Second try
menuItems[0].active = false;
menuItems.unshift(newMenuItem);
state = Object.assign({}, state, {menuItems: Object.assign([], menuItems)});
// Third try
menuItems[0].active = false;
state = (Object.assign({}, state, {
menuItems: [
Object.assign({}, newMenuItem),
...menuItems
]
}));
// Fourth try
menuItems[0].active = false;
state = update(state, {
menuItems: {$unshift: new Array(newMenuItem)}
});
console.log(state);
return state;
}
}
}
In the fourth try, I am using React's Immutability Helpers but it never works. I logged the state to the console before returning the state and it logs correctly, but when logging inside the components which get re-rendered
, the menuItems array does not add the first item, although the active
member is set to false
.
What could I be doing wrong?
Answer
The state in the reducer should be immutable and for this reason should not be modified. It is also recommended to flatten your object whenever possible.
In your scenario your initial state could be an array as such:
[{
href: '/',
active: true
}]
In your reducer, try returning a brand new array as follows:
function reducer(state = {}, action) {
switch (action.type) {
case ADD_MENU_ITEM: {
return [
action.newMenuItem,
...state.map(item => Object.assign({}, item, { active: false }))
];
}
}
}
More information about reducers can be found here: Redux Reducers Documentation
Helpful excerpt from the documentation:
It’s very important that the reducer stays pure. Things you should never do inside a reducer:
- Mutate its arguments;
- Perform side effects like API calls and routing transitions;
- Calling non-pure functions, e.g. Date.now() or Math.random().
MORE INFO ADDED
In your reducer, and for all four tries, you are modifying the existing state before returning it.
This results in react-redux
, when checking if your state has changed, not to see any changes as both the previous and next states are pointing to the same object.
Here are the lines I am referring to:
First Try:
// This line modifies the existing state.
state = Object.assign({}, state, { menuItems: menuItems });
Second Try:
// This line modifies the existing state.
state = Object.assign({}, state, {menuItems: Object.assign([], menuItems)});
Third Try:
// This line modifies the existing state.
state = (Object.assign({}, state, {
menuItems: [
Object.assign({}, newMenuItem),
...menuItems
]
}));
Fourth Try:
// This line modifies the existing state.
state = update(state, {
menuItems: {$unshift: new Array(newMenuItem)}
});
Related Questions
- → Import statement and Babel
- → should I choose reactjs+f7 or f7+vue.js?
- → Uncaught TypeError: Cannot read property '__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' of undefined
- → .tsx webpack compile fails: Unexpected token <
- → React-router: Passing props to children
- → ListView.DataSource looping data for React Native
- → React Native with visual studio 2015 IDE
- → Can't test submit handler in React component
- → React + Flux - How to avoid global variable
- → Webpack, React & Babel, not rendering DOM
- → How do I determine if a new ReactJS session and/or Browser session has started?
- → Alt @decorators in React-Native
- → How to dynamically add class to parent div of focused input field?