61

Looking at this, I think that Immutable. Record is the data structure for represent "javascript immutable objects", but I want to update several fields at once without creating several objects calling set each time.

I want to do something like this

class LoginContext extends Immutable.Record(
{ logged : false, loading: false, error: false, user: null}){
}

var loginContext = new LoginContext()

var anotherContext = loginContext.set({'logged':'true', 'error':'false'})

I read that you can't pass an object to Record.set() for API consistency:

Consistency with other uses of set both in this library and in ES6. Map and Set's set can't accept an object, because their keys can be anything, not just strings. Records must have strings, but keeping the API consistent was important.

And I know that I could use:

var anotherContext = loginContext.withMutations(function (record) {  
  record.set('logged','true').set('error','true'); 
});

There is another way or I'm misusing Record?

Yves M.
  • 29,855
  • 23
  • 108
  • 144
gabrielgiussi
  • 9,245
  • 7
  • 41
  • 71

4 Answers4

60

Personally I prefer the merge syntax, it feels more natural:

const newContent = oldContext.merge({
    "logged": true, 
    "error": false
});

If you were doing something very complex, maybe the transient version would be better, but I just can't imagine where.

It also means you can leverage mergeDeep if needed.

Chris
  • 54,599
  • 30
  • 149
  • 186
  • If you are *sure* you will always merge flat dicts, maybe. But I really don't recommend it. See my comment on RemEmber's answer. – Mike Gleason jr Couturier Nov 28 '16 at 15:33
  • Interesting, can you give an example of where a non-flat dictionary would lead to bugs by using merge? – Chris Nov 29 '16 at 01:03
  • 3
    `var a = new Immutable.Map({a:{b:1}});` now `a.get('a')` is a POJO. Then `var b = a.merge({a:{b:2}});`. Now `b.get('a')` is an `Immutable.Map`. – Mike Gleason jr Couturier Nov 29 '16 at 02:15
  • Fair call. And interesting, because `set` doesn't suffer from that. I haven't come across this as an issue, as my state is loaded up via `fromJS`, but is something to be aware of – Chris Nov 29 '16 at 02:23
  • The documentation is clear about `merge`, it will make an `Immutable` out of any `Iterable` found. While `set` is just a "dumb" set. `fromJS` will behave like `merge`. – Mike Gleason jr Couturier Nov 29 '16 at 02:51
  • 2
    Yep exactly - so, because I use `fromJS` and `merge` consistency throughout my apps, I haven't come across this as an issue. As I said, something to be aware of – Chris Nov 29 '16 at 02:54
  • 2
    I have used `merge()` for this - and you can combine with `fromJS` if you want to handle "non-flat" values: `merge(fromJS({a:{b:1}}))`. Then nested values will always be ImmutableJS values. – thom_nic Dec 02 '16 at 15:46
  • 1
    If you are worried about performance, note using merge like this is significantly slower than multiple set operations. – JustinLovinger Aug 14 '18 at 03:07
  • @PaintingInAir can we see the data for this? And at what point does it become even slightly noticeable? Be careful prematurely optimising, it usually leads to people being "too clever" and less readable – Chris Aug 14 '18 at 03:12
  • @Chris Here is the benchmark code: https://pastebin.com/Mxk3UB86. Multiple set operations is about 20 times faster than merge with 2 updates. It is also worth nothing that merge actually uses withMutations under the hood. – JustinLovinger Aug 14 '18 at 03:25
  • Yes, but when does it become significant? I ran your tests @PaintingInAir , and in one run I get `multiple set x 939,074 ops/sec` and `merge x 818,842 ops/sec`. To me, in the work I do, the different in performance would be completely unnoticeable. So for me, I would use merge because it is more readable. – Chris Aug 14 '18 at 09:43
  • @Chris I imagine the speed up depends on your environment. In my node tests, multiple set (with 2 sets) gets about 5,000,000 ops/sec to about 250,000 ops/sec for merge. As for when it comes significant, that depends on your program and performance requirements. I cannot answer that objectively. I'm just here to point out the performance difference, because I imagine a number of people come by this question because they are used to the high cost of creating a copy of an object associated with mutable objects, which does not apply to immutable.js. – JustinLovinger Aug 14 '18 at 20:39
  • @PaintingInAir Yes exactly, that is my point. It absolutely depends on a number of factors, including your environment, the quantity and frequency your app updates data, etc. You should fix performance when performance becomes an issue. To put it another way, I've never seen an immutable merge be the bottleneck, it's always further down like updating the DOM. Again, you need to fine tune when appropriate and when its causing an issue – Chris Aug 14 '18 at 21:46
28

To answer your original question we will use .withMutations()

Here an excerpt from the docs:

Applying a mutation to create a new immutable object results in some overhead, which can add up to a minor performance penalty. If you need to apply a series of mutations locally before returning, Immutable gives you the ability to create a temporary mutable (transient) copy of a collection and apply a batch of mutations in a performant manner by using withMutations. In fact, this is exactly how Immutable applies complex mutations itself.

So you can write something along the lines:

loginContext.withMutations((ctx) => {
    ctx.set('logged', true).set('error', false)
});

Besides that I thought that Records could also be used with ordinary js dot notation.

Good luck!

Boris Zagoruiko
  • 12,705
  • 15
  • 47
  • 79
RemEmber
  • 665
  • 7
  • 14
19

Why don't you just chain multiple sets? Like so:

ImmutableObj.set('key', 'value')
.set('key2', 'value2')
.set('key3', 'value3'); 
James111
  • 15,378
  • 15
  • 78
  • 121
  • 6
    _I want to update several fields at once without creating several objects calling set each time_ – gabrielgiussi Aug 04 '16 at 11:54
  • Oh right. I guess this is an alternative to the other answers here @gabrielgiussi – James111 Aug 04 '16 at 11:57
  • If you are worried about performance, it is worth noting that multiple set operations is significantly faster than using merge and creating an object, and only marginally slower than withMutations. – JustinLovinger Aug 14 '18 at 03:03
1

You should do a few things differently. First, don't extend Immutable.Record but use the constructor:

var LoginContext = Immutable.Record({
  logged:false,
  loading:false,
  user: null  
}, 'LoginContext');

This returns a class that you can instantiate with an object:

var anotherContext = new LoginContext({logged:true, error:false});
Matthias Winkelmann
  • 15,870
  • 7
  • 64
  • 76