-1

Given an immutable state like this:

alerts: {
  5a8c76171bbb57b2950000c4: [
    {
      _id:5af7c8652552070000000064
      device_id:5a8c76171bbb57b2950000c4
      count: 1
    },
    {
      _id:5af7c8722552070000000068
      device_id:5a8c76171bbb57b2950000c4
      count: 2
    }
  ]
}

and an object like this:

{
   _id:5af7c8652552070000000064
   device_id:5a8c76171bbb57b2950000c4
   count: 2
}

I want to replace the object with the same id in the alerts state (immutable), such that end result looks like this:

alerts: {
  5a12356ws13tch: [
    {
      _id:5af7c8652552070000000064
      device_id:5a8c76171bbb57b2950000c4
      count: 2
    },
    {
      _id:5af7c8722552070000000068
      device_id:5a8c76171bbb57b2950000c4
      count: 2
    }
  ]
}

How can I do that? With mergeDeep, getIn, setIn, and updateIn, found on List, Map or OrderedMap ?

I tried doing something like this.. where index is 0 and deviceId is 5a12356ws13tch

Does not work though.

export const oneAlertFetched = (state, {deviceId, index, alert}) => state.setIn(['alerts', deviceId, index], alert).merge({fetching: false})

I tried this as well. Does not work.

export const oneAlertFetched = (state, {deviceId, index, alert}) => {
  const a = state.alerts[deviceId][index]
  state.alerts[deviceId][index] = Object.assign({}, a, alert)
  return
}
Lalaluye
  • 71
  • 1
  • 9
  • 4
    Immutable implies that you can't change it... I don't see anything specifically about your object that makes it immutable, but if you are saying you consider the input object immutable, it implies you can't 'replace' anything inside of it. – Evert Jun 07 '18 at 21:27
  • @Evert: You didn't understand the question. The object does NOT change. She wants to replace one immutable object in the array with another immutable object. – mentallurg Jun 07 '18 at 21:31
  • You should check if `count` is **writable**. Log this: `Object.getOwnPropertyDescriptor(alerts['5a12356ws13tch'][0])`. What do you see? – RaphaMex Jun 07 '18 at 21:44
  • @RaphaMex I get the first object. Updated my question with what I tried. – Lalaluye Jun 07 '18 at 22:05
  • I know, I wanted to see the descriptor. Please check my answer :) – RaphaMex Jun 07 '18 at 22:10
  • 1
    @mentallurg that's not obvious from the question. The entire thing could be considered an immutable state, not just specific elements inside of it. – Evert Jun 07 '18 at 22:15
  • How is your update function supposed to know to look into the `5a12356ws13tch` key specifically? it seems `device_id` is a different id value in your objects – Dominic Jun 07 '18 at 22:16

3 Answers3

1

By immutable, you mean that your property is non-writable.

If you want to modify your object in-place (not recommended), you will need the property to be at least configurable:

const device = alerts['5a12356ws13tch'][0];

if (Object.getOwnPropertyDescriptor(device, 'count').configurable) {
    // Manually make it `writable`
    Object.defineProperty(device, 'count', {
        writable: true
    });

    // Update property's value
    device.count++;

    // Set it back to `non-writable`
    Object.defineProperty(device, 'count', {
        writable: false
    });
}
console.log(device.count); // 2

If it is not configurable (cannot make it writable), or you do not want to jeopardize your application (it must be non-writable on purpose), then you should work on copies.

const device = alerts['5a12356ws13tch'][0];
alerts['5a12356ws13tch'][0] = Object.assign({}, device, {count: device.count + 1});

Object.assign() works on flat objects. If you need deep copy, have a look at my SO answer there.

RaphaMex
  • 2,781
  • 1
  • 14
  • 30
  • I tried this method. Please see my updated post. Am I doing something wrong here because it's not working. – Lalaluye Jun 07 '18 at 23:04
  • @Lalaluye: I see nothing wrong with your code. Can you make a [jsfiddle](https://jsfiddle.net/) to reproduce? – RaphaMex Jun 07 '18 at 23:55
0

I think you mean you want to return a new object with the updated payload?

function getNextAlerts(alerts, parentDeviceId, payload) {
  const alertsForDevice = alerts[parentDeviceId];
  
  if (!alertsForDevice || alertsForDevice.length === 0) {
     console.log('No alerts for device', deviceId);
     return;
  }

  return {
    ...alerts,
    [parentDeviceId]: alerts[parentDeviceId].map(item =>
      item._id === payload._id ? payload : item
    ),
  }
}

const alerts = {
  '5a12356ws13tch': [
    {
      _id: '5af7c8652552070000000064',
      device_id: '5a8c76171bbb57b2950000c4',
      count: 1
    },
    {
      _id: '5af7c8722552070000000068',
      device_id: '5a8c76171bbb57b2950000c4',
      count: 2
    }
  ]
};

const nextAlerts = getNextAlerts(alerts, '5a12356ws13tch', {
   _id: '5af7c8652552070000000064',
   device_id: '5a8c76171bbb57b2950000c4',
   count: 2,
});

console.log('nextAlerts:', nextAlerts);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Dominic
  • 62,658
  • 20
  • 139
  • 163
  • I do not see the need for lodash here, OP's objects are flat. Deep merging is extensively covered by other SO threads. – RaphaMex Jun 07 '18 at 22:32
  • It's unclear if she wants a deep merge, but I've updated without lodash for a merge that replaces the array and the item, rather than all the items which is more optimal and will be better for e.g. React pure reconciliation – Dominic Jun 07 '18 at 22:33
  • No, I need to replace the current immutable object with the new object added in – Lalaluye Jun 07 '18 at 23:03
  • Did you run the code? it replaces the object in the array with the new one and also returns a new array – Dominic Jun 08 '18 at 08:20
0

If you're working with plain JavaScript objects and want to keep "immutable" approach you have to use spreads all over the nested structure of state object.

But, there are some tools already targeting this issue - lenses.

Here is the example of both approaches, array/object spreads and lenses - ramda repl.

In short, your example via spreads:

const oneAlertFetched = (state, { deviceId, index, alert }) => ({
  ...state,
  alerts: {
    ...state.alerts,
    [deviceId]: [
      ...state.alerts[deviceId].slice(0, index),
      { ...state.alerts[deviceId][index], ...alert },
      ...state.alerts[deviceId].slice(index + 1)
    ],
  }
})

And via lenses using Ramda's over, lensPath, merge and __*:

const oneAlertFetched = (state, { deviceId, index, alert }) =>
  R.over(
    R.lensPath(['alerts', deviceId, index]),
    R.merge(R.__, alert),
    state
  )

* R.__ placeholder used to swap 1st & 2nd parameters of R.merge

PS: lenses solution is intentionally adjusted to match the declaration of your function, so you can easily compare two approaches. However, in real life, with such powerful and flexible tool, we can rewrite the function to be more readable, reusable, and performant.

amankkg
  • 4,503
  • 1
  • 19
  • 30