Search code examples
reactjsreact-reduximmutabilityimmutability-helper

ReactJS - Proper way for using immutability-helper in reducer


I have the following object which is my initial state in my reducer:

const INITIAL_STATE = {
  campaign_dates: {
    dt_start: '',
    dt_end: '',
  },
  campaign_target: {
    target_number: '',
    gender: '',
    age_level: {
      age_start: '',
      age_end: '',
    },
    interest_area: [],
    geolocation: {},
  },
  campaign_products: {
    survey: {
      name: 'Survey',
      id_product: 1,
      quantity: 0,
      price: 125.0,
      surveys: {},
    },
    reward: {
      name: 'Reward',
      id_product: 2,
      quantity: 0,
      price: 125.0,
      rewards: {},
    },
  },
}

And my reducer is listening for an action to add a reward to my object of rewards:

    case ADD_REWARD:
      return {
        ...state, campaign_products: {
          ...state.campaign_products,
          reward: {
            ...state.campaign_products.reward,
            rewards: {
              ...state.campaign_products.reward.rewards,
              question: action.payload
            }
          }
        }
      }

So far so good (despite the fact that every object added is named "question")... its working but its quite messy. I've tried to replace the reducer above using the immutability-helper, to something like this but the newObh is being added to the root of my state

case ADD_REWARD:
   const newObj = update(state.campaign_products.reward.rewards, { $merge: action.payload });
   return { ...state, newObj }

Solution

  • return { ...state, newObj }
    

    First, you must understand how the object shorthand works. If you're familiar with the syntax before ES2015, the above code translates to:

    return Object.assign({}, state, {
      newObj: newObj
    });
    

    Note how the newObj becomes a key and a value at the same time, which is probably not what you want.

    I assume the mentioned immutability-helper is this library: https://www.npmjs.com/package/immutability-helper. Given the documentation, it returns a copy of the state with updated property based on the second argument.

    You're using it on a deep property so that it will return a new value for that deep property. Therefore you still have to merge it in the state, so you have to keep the approach you've labelled as messy.

    What you want instead is something like:

    const nextState = update(state, {
      $merge: {
        campaign_products: {
          reward: {
            rewards: action.payload
          }
        }
      }
    });
    
    return nextState;
    

    Note how the first argument is the current state object, and $merge object is a whole object structure where you want to update the property. The return value of update is state with updated values based on the second argument, i.e. the next state.

    Side note: Working with deep state structure is difficult, as you've discovered. I suggest you look into normalizing the state shape. If applicable, you can also split the reducers into sub-trees which are responsible only for the part of the state, so the state updates are smaller.