0

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 }
fjurr
  • 541
  • 2
  • 8
  • 23
  • 2
    https://immerjs.github.io/immer/docs/introduction take a shot for immer.js, really cool tool for immutable reducers, simple to learn and execute – Oner T. Dec 28 '19 at 20:16
  • I have answered the problem below, but I agree with @OnerT.'s suggestion. From my experience, Immer is more intuitive as it allows you basic JS syntax instead of forcing you into special notation. – Marko Gresak Dec 28 '19 at 20:40
  • Thanks! I'll definitely going to check immer. – fjurr Dec 28 '19 at 21:31

1 Answers1

2
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.

Marko Gresak
  • 7,950
  • 5
  • 40
  • 46