Updating string in redux store - reactjs

I am using redux with react. I have a key in store whose value is a string.
This is what is get in store.getState();
{appBgColor: "#FFF"}
I am updating the store like this.
store.dispatch( changeAppBgColor("#FC0") );
Reducer
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_APP_BG:
return { ...state, appBgColor: action.payload};
default:
return state;
}
};
Everything works fine, the appBgColor get changed whenever a new dispatch happens.
Problem
I have read that to change a key in redux store you should use concat, slice or join to make the state immutable. So I doubt I am doing it wrong. Is this the correct way to do it?

For your use-case of updating the field appBgColor within your state, your reducer implementation is correct.
Note that when you return { ...state, appBgColor: action.payload}, you are not mutating the state, but in fact, creating a copy of the existing state, applying the change and returning it. This makes sure that the state is immutable, ie it is not directly modified.
You would only need to use functions like slice, concat etc when you are updating nested items within your state. For eg, when you need to remove an element from an array within your state, you would need to use slice as shown below.
const index = state.findIndex(a => a.id === action.id)
return [
...state.slice(0, index), ...state.slice(index + 1)
]

Related

React redux-strange behavior, prevState and newState change together

I started to work on some frontend project, I needed to plant foundation and I choosed to go with react and redux. I went through some tutorials and started to build my project.
Everything was working fine, but all of sudden I m stuck with problem I'm not even sure how to describe it.
Problem is connected with using redux, reducers.
In short somehow my prevState was changing with the newState. The worst part is I cant share much more information, I'm new to this react/redux, so I'm not even sure if this is even possible.
Here is the example code:
export default function (state: UserReducerState = initialState, action: Action): any {
const newState = Object.assign({}, state);
console.log(state);
state.loginStatus.username="This change should only be on prevState";
console.log("newState",newState);
return newState;
};
When I console.log(newState) it has the change from the old state even though the newState was copied before the state.loginStatus.username ="This change......."
I dont know what code should I show more, but this is where problem start,I think.
How is even possible that object that is copied have the refrence to the old one ?
Issue
Even though you are copying the state object it isn't a deep copy. You are mutating the more deeply nested properties of your previous state object since the new state is simply holding a reference to them.
state.loginStatus.username = "This change should only be on prevState";
newState is a new object, but newState.loginStatus.username is reference back to the same object the previous state holds.
Solution
I'm not sure why you'd mutate your old state object in a reducer, but when updating the more deeply nested properties in the new state you must shallow copy any nested state that is being updated. Typically this is achieved via the spread syntax.
export default function (state: UserReducerState = initialState, action: Action): any {
const newState = {
...state,
loginStatus: {
...state.loginStatus,
username: "This change should only be on newState".
},
};
console.log(state);
console.log("newState", newState);
return newState;
};

Immutability in react redux reducer

If I write my reducer in the following way, then the "render" method is getting called and its a expected behavior. No problem here :
const initState = {
entries: ["test1"]
};
export const EntryReducer = (state = initState, action) => {
switch (action.type) {
case ADD_ENTRY:
return Object.assign({}, state, {
entries: state.entries.concat("test2")
});
break;
case DELETE_ENTRY:
break;
}
return state;
}
But, if I write the reducer in the following way, then the "render" method is not getting called though state is being updated :
export const EntryReducer = (state = initState, action) => {
let newState = Object.assign({}, state);
switch (action.type) {
case ADD_ENTRY:
newState.entries.push("test2");
break;
case DELETE_ENTRY:
break;
}
return newState;
}
I could not understand why render is not getting called. As far as I understand, "newState" is a immutable object and does not hold any reference to the "state" object.
Please help me understand it. Thanks.
Because Object.assign is shallow, it will not create a new Array, the old one will be mutated by .push():
state.entries === newState.entries // ["test1", "test2"]
"newState" is a immutable object
If you don't do that on your own, it's ordinary object/array/etc so it is not immutable.
why render is not getting called
React-redux tries its best and actually wraps your component into PureComponent. It does that to make all your connect()-ed components will not re-render on any action called but only once store has been updated in relevant places.
To realize if relevant data has been changed or not, PureComponent compares shallowly like oldDataProp !== newDataProp. Once you mutate objects/arrays that check will fail so component will not re-render.
It has nothing to do with the way you update the state. Consider it correct, though I would not recommend to avoid common best practices.
In your case render is not called because component props is not changed...
I believe that you may have something like this:
<Component entries={this.props.entries}/>
and
mapStateToProps = (state) => ({
entries: state.entries
})
If it is so, then state.entries is the prop that controls whether your component will be re-rendered or not. If it has the same value during the state change on ADD_ENTRY action - the component will not be re-rendered.
So. Get back to roots. Remember that in JavaScript state.entries is a POINTER, that points to array in memory.
While you calling entries.push the array in memory will be extended with another element - thats for sure. BUT the POINTER value that we have in state.entries will remain the same. It will be not changed. Thats how Array.push works.
As the result <Component entries={this.props.entries}/> will not be re-rendered.
By changing newState.entries.push("test2");
to
newState.entries = newState.entries.concat("test2");
you actually changing entries pointer, component sees that property props.entries has changed and now the whole thing re-renders...
You need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value.
You should use a Deep clone.
let newState= JSON.parse(JSON.stringify(state));

Redux reducer prevState already set up to newState

I am using redux to move search filter through application to components.
I have component where I set up filters and call dispatch function using
this.props.dispatch(setFilter(newFilter));
I use sliders / range components to set up values, those components have handleAfterChange method which is set up to call mentioned dispatch function. This is working fine.
I have also filters that are set up by clicking buttons, I created onClick handler, and this handler call mentioned function. I checked what I am sending to setFilter functions (newFilter) parameter and it is what I want to set up.
My set action is defined:
export const setFilter = (filter = {}) => {
return {
type: SET_FILTER,
filter
};
};
My reducer is:
const searchFilter = (prevState = INITIAL_STATE.filter, action) => {
switch (action.type) {
case SET_FILTER: {
// prevState is already the state
// I want to set up in this reducer
console.log(prevState);
return Object.assign({}, prevState, action.filter);
}
case RESET_FILTER: {
return INITIAL_STATE.filter;
}
default: {
return prevState;
}
}
};
Problem is that prevState is already the object I want to set up in reducer.
I am checking (in another component) if filter has been changed and because of that I get prevProps and nextProps the same so no action will be triggered.
So somehow dispatched changes are already in reducer as prevProps.
In searchFilter reducer,you are returning the prevState as it is without mutating.
return prevState //which is reference to prevState = INITIAL_STATE.filter
correct way (no sure about state structure),
default: {
return {...prevState};
}
OR
return Object.assign({}, prevState);
#RIYAJKHAN was partially right.
Somehow I was modifying redux store directly, even if I made all object modifications via Object.assign(...).
In the end I had to create copy of object I was modifying - JSON.parse(JSON.stringify(filter)) and then update this new object and dispatch it to redux store

What is the best practice to return a new state from reducers in react/redux?

What is the best practice to return a new state from reducers - is it by using $set and update from immutability-helpers, or by using Object.assign()?
Personally I wouldn't use immutability helpers when working on a project where it's easy to manage the state. If you have a really large state it can be helpful to use immutability helpers so you won't be that likely to make mistakes.
And for returning the state in a reducer, it's a common practice to return the new state by using the spread operator. This would look something like this:
return {...state, newProp: 'value', etc...}
you can create new state in reducer in redux and also change the state by using set method and you can return into your index page as props
ex:in reducer you write as follows
return state.set('tasks', action.response)
At first, you should need a clear understanding of redux. it's a back store on your frontend. redux store data as a stacking technique FIFO or LIFO as you defined. You don't make it reassign, just return with action-object using ES6 SPREAD operator and using destructuring. I try to give an example below:
const postsReducerDefaultState = [];
const postsReducer = (state=postsReducerDefaultState, action) => {
switch(action.type) {
case 'ADD_POST':
return [action.post, ...state];
case 'REMOVE_POST':
return state.filter(post => post.id !== action.id)
case 'SET_POSTS':
return action.posts;
default:
return state;
}
};
export default postsReducer;
ADD_POST: return [action.post, ...state] latest first then spreading your old post.SET_POSTS: return action.posts that you have passed with your action. If you want to set an object property then use destructuring
return {
...state,
text:action.text
}
text property already have in your state. if haven't then it will add a property to your state. I think it helps you to understand something redux.

In Redux, is it necessary to do deep copy

The below object is action.data has a nested object address
{
name: 'Ben',
address: {
country: 'Australia',
state: 'NSW'
}
}
How should I handle it in the reducer?
const rootReducer = (state = initState, action) {
switch(action.type) {
switch RECEIVE_DATA:
return {...state, data: action.data}
}
}
Can I do it as above? that I just assign the whole object to data without copying?
or
const rootReducer = (state = initState, action) {
switch(action.type) {
switch RECEIVE_DATA:
const address = {...action.data.address}
const data = {...action.data, address}
return {...state, data}
}
}
Or should I do a deep copy of the object and assign it to data?
thanks
The "correct" way to handle updates of nested data is with multiple shallow copies, one for each level of nesting. It's also certainly okay to create a new object that just replaces one field completely, per your first example.
See the Redux docs section on Immutable Update Patterns for some info on how to properly do immutable updates, and also the Redux FAQ entry regarding deep cloning.
From Redux:
Common Redux misconception: you need to deeply clone the state. Reality: if something inside doesn't change, keep its reference the same!
No, shallow copy of Unchanged properties.Changed properties will be anyway new values, so no question of type of copy.
In code we achieve it like this return {...prevState}
If you are changing only one item in an array, Redux docs says you can use array.map, but if you know the index, this is faster:
state[action.args.index] = {
...state[action.args.index],
disregardLeafNode: action.args.checked
}
return state

Resources