I have a simple reducer/context system to store states. Everything is working fine except that I cannot update fields individually without affect the state of the others.
The problem is that, when I change one of the AppData
fields, the other fields go blank. I don't know what I am doing wrong and I would like to be able to update an individual value whilst preserving the value of the other fields.
For example: if I only update the appData.headerTitle
value, then the appData.testValue
and appData.userHasLoggedOut
values will get erased. If I update the 3 values at the same time, then nothing will be erased. I want to be able to update only one field without erasing the rest.
export function AppReducer(state: AppState, action: Actions): AppState {
switch (action.type) {
case 'setAppData':
return {
...state,
appData: action.payload
};
case 'changeAppData':
return {
...state,
appData: {
...state.appData,
headerTitle: action.payload,
testValue: action.payload,
userHasLoggedOut: action.payload
}
};
}
}
Sandbox with the full working code: https://codesandbox.io/s/frosty-cache-c6yqgx?file=/src/middlewares/reducer/AppReducer.tsx
Thank you!
I'm not sure what to try here...
CodePudding user response:
Every time you make a change you are sending a payload which is an object with ONE property e.g. in App.tsx
line 22 it is an object with one property headerTitle
.
So this object in AppReducer.tsx
line 9 overwrites whole current state so if there were any other properties they will be overwritten and disappear.
To fix this issue I suggest you spread this payload to save previous fields with data:
case "setAppData":
return {
...state,
appData: {
...state.appData,
...action.payload
}
};
Hint: I actually dont know why there is a appData field in this state. Maybe it would be easier and cleaner if the properties where directly in the state. I mean, instead of this structure
export type AppState = {
appData: {
headerTitle?: string;
testValue?: string;
userHasLoggedOut?: boolean;
};
};
use something like that:
export type AppState = {
headerTitle?: string;
testValue?: string;
userHasLoggedOut?: boolean;
};
CodePudding user response:
There's a couple things wrong with your changeAppData
case. Also feels like some parts are missing in your code, for example the changeAppData
fn isn't being returned in your useContext hook. But with what I can see, all three of your appData
values will be the same when you hit the changeAppData
case because they are all set to action.payload
.
You can, (and I would) break up your changeAppData
action here. I'm looking at your codesandbox, you're actually not updating multiple values at once. You're updating pieces of state individually, so you may as well have more descriptive actions.. On top of that, you don't really need this extra appData
object layer nested inside the state. So maybe something like below
switch (action.type) {
case 'SET_HEADER_TITLE':
return {
...state,
headerTitle: action.payload,
};
case 'SET_USER_LOGGED_OUT':
return {
...state,
userHasLoggedOut: action.payload
};
default: // Don't forget your default case
break;
}
If you insist on nesting them under another object, in this case appData
. You'll need to grab the state object from your reducer, and then pass that object in with the update key/property.
switch (action.type) {
case 'setAppData':
return {
...state,
appData: action.payload
};
case 'UPDATE_APP_DATA':
return {
...state,
appData: action.payload
};
default: // Don't forget your default case
break;
}
And then something like:
changeAppData({
...state,
testValue: 'Updated Test Value Here'
})
EDIT: I just realized that the UPDATE_APP_DATA
case I made is the exact same as your setAppData
.. Haha, okay so you only need the first case. However I probably wouldn't do it like this because you'll need to pass in the entire object every time.