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
Related
Can I update the state based on other state variables (as shown below in setCurrentQuestion()), or will it cause unexpected issues?
I can think of two workarounds:
"external" helper function that calls a getCurrentQuestionIndex() selector passed into a getQuestionByIndex() selector which returns an IQuestion object that I pass into the setCurrentQuestion() reducer
Keep as is but use a draft safe selector instead of accessing the state object directly
const slice = createSlice({
name: 'blabla',
initialState: { questions: undefined, currentQuestion: undefined, noOfQuestions: 0, currentQuestionIndex: 0 } as QuizState,
reducers: {
setQuestions: (state, action: PayloadAction<IQuestion[]>) => {
state.questions = action.payload;
},
setCurrentQuestion: (state, action: PayloadAction<number>) => {
if (!state.questions) return
state.currentQuestion = state.questions[state.currentQuestionIndex]
}
},
state.currentQuestion should simply not exist in your state. You got state.questions as the source of truth, and state.currentQuestionIndex to remember which question is the current question. Anything further is not atomic and minimal anymore. It certainly works to have a state.currentQuestion which duplicates one of the items in state.questions, but then you end up with problems like this one. I'd recommend to add a selector getCurrentQuestion() that does the lookup and that can deal with state.questions being falsy. It also should never not be an array btw, it's easier to deal with state if the types don't change. I'd initialise it as an empty array, and then the selector could be:
const getCurrentQuestion = (state) => state.questions[state.currentQuestionIndex];
To solve what you had in mind, looking at a different part of the state before mutating it in the reducer, you could also use a thunk - they have getState() in the scope and can run conditional logic. So you could simply not dispatch setCurrentQuestion if state.questions is falsy. But a better solution imo is what I described above with the selector.
I have this reducer.
const userInitialState = {
users: [],
};
const users = (state = userInitialState, action) => {
if (action.type === "FETCH_USERS") {
return {
...state,
users: action.payload,
};
}
return state;
};
export default combineReducers({
users,
});
initially the users property is edmpty array,when the new results from the api call comes
for example response like
https://jsonplaceholder.typicode.com/users
is this the correct way for immutable way in redux store for my array inside ?
A proper immutable update is best described as "a nested shallow clone". You don't want to copy every value in a nested data structure - just the ones that need to be updated.
But yes, that looks correct.
A couple additional observations:
You should read through the post The Complete Guide to Immutability in React and Redux and the Redux docs page on Immutable Update Patterns to better understand how to do immutable updates correctly
But, you should really be using our official Redux Toolkit package, which uses Immer to let you write "mutating" update logic that is turned into safe and correct immutable updates.
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));
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)
]
I just got started with React+Redux and I have a problem.
I know that I am never supposed to alter the old state in the reducer and I am not doing that.
However, when I change a variable like this in my reducer, my component is not re-rendering, even though I have mapStateToProps with state.coupons
// this deep copies everything
let newState = Object.assign({}, state);
newState.coupons[2].events[0].eventRows[0].alternatives[0].selected = true;
return newState;
What am I doing wrong?
EDIT:
I even tested to use newState = JSON.stringify(JSON.parse(oldState)) but with no success
EDIT:
This is my mapStateToProps function
const mapStateToProps = (state, ownProps) => ({
coupons: state.coupons,
currentDraw: state.currentDraw
});
You can find the solution in redux docs: http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html#updating-nested-objects
The key to updating nested data is that every level of nesting must be
copied and updated appropriately. This is often a difficult concept
for those learning Redux, and there are some specific problems that
frequently occur when trying to update nested objects. These lead to
accidental direct mutation, and should be avoided.
You can do this manually, something like:
function updateVeryNestedField(state, action) {
return {
....state,
first : {
...state.first,
second : {
...state.first.second,
[action.someId] : {
...state.first.second[action.someId],
fourth : action.someValue
}
}
}
}
}
In practice, it's better to use a helper library to do this. You can find a list of helper libraries at https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities, I would personally recommend immutability-helper or just switching to immutable.js.