Home > Software engineering >  React Context states getting erased automatically and unable to update individual fields
React Context states getting erased automatically and unable to update individual fields

Time:11-01

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.

  • Related