'How to update state ( initial State ) objects in reducer when using immer?

If have a state of objects payload:

    export const initialState = {
      payload: {
       firstName: 'Mick',
       lastName: 'Andri',
       phoneNumber: '651-332-5675',
       electronicMailAddress: '[email protected]',
       addressLine1: '27 Crest Ave',
       addressLine2: '',
       cityName: 'San Jose',
       stateCode: 'CA',
       zipCode: '92122',

   },
};

and to load this state it in some way I using immer:

const SettingsReducer = (state = initialState, action) =>
    produce(state, (draft) => {
    switch (action.type) {
      case actionTypes.LOAD_SETTINGS_INFO:
        draft.payload = action.payload;
        break;
    }
  });

export default SettingsReducer;

If we were to do this the usual way with JavaScripts object and array spread syntax, our state reducer might look like below, this is just an example:

 draft.payload = action.payload;
   case 'UPDATE_SETTINGS_INFO':
return {
   ...state,
   payload: [...state.payload, action.payload],
};

In my case:

I getting all fields through the react hook form and trying to update my state. I need to update the state without losing the whole data.

The code below works but deletes the rest of the data from the state.

case actionTypes.UPDATE_SETTINGS_INFO:
        draft.payload = action.payload;
        break;

I need to update half of the state at one time.

What is the best way to clone the state without losing the whole data?



Solution 1:[1]

First, I would recommend not having a state.payload field. "Payload" is normally used to describe "the contents of an action object", and as a term it really doesn't make sense for state in a reducer.

Second, it sounds like what you're trying to do here is merge two objects together, such that fields in the second override fields in the first. If we were doing an immutable update (and assuming that state itself is the original data), we could do return {...state, ...action.payload}.

With Immer, you can "mutate" the object directly:

state.a = action.payload.a;
state.b = action.payload.b;
// etc

If there's multiple fields you want to update at once, and action.payload will contain some or all of the fields in the original state, you can do this with Immer:

// Object.assign always mutates its first argument
Object.assign(state, action.payload);

Finally, I'd specifically recommend using our official Redux Toolkit package, which already uses Immer inside of its createSlice API:

const settingsSlice = createSlice({
  name: 'settings',
  initialState,
  reducers: {
    settingsLoaded(state, action) {
      Object.assign(state, action.payload)
    }

  }
});

See the Redux tutorials for more details:

https://redux.js.org/tutorials/index

Solution 2:[2]

You made a payload object within your initial state object, so using markerikson's setup, if you change any of the settings, I believe that entire payload object will be replaced. Your initial state should be:

export const initialState = {
   firstName: 'Mick',
   lastName: 'Andri',
   phoneNumber: '651-332-5675',
   electronicMailAddress: '[email protected]',
   addressLine1: '27 Crest Ave',
   addressLine2: '',
   cityName: 'San Jose',
   stateCode: 'CA',
   zipCode: '92122',
};

Then you should be able to change any of the individual settings in that initialState object using:

dispatch(settingsLoaded(changedState))

and as an example:

const changedState = {
   cityName: 'Bozeman',
   stateCode: 'MT',
   zipCode: '94565',
}

and it will persist your changes along with everything you did not change, if I'm understanding your question correctly.

Solution 3:[3]

This solution works for me

case actionTypes.UPDATE_SETTINGS_INFO_FAILURE:
        draft.errorMessages = [...action.payload];
        break;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 markerikson
Solution 2 tjacqu2
Solution 3 Ivan K.