rtfeldman / seamless-immutable Goto Github PK
View Code? Open in Web Editor NEWImmutable data structures for JavaScript which are backwards-compatible with normal JS Arrays and Objects.
License: BSD 3-Clause "New" or "Revised" License
Immutable data structures for JavaScript which are backwards-compatible with normal JS Arrays and Objects.
License: BSD 3-Clause "New" or "Revised" License
Hello. Thanks for seamless. It's a very nice piece of software.
I was surprised however that I cannot create immutable object from an object without prototype (e.g. acquired with Object.create(null)
:
Immutable(Object.create(null));
// TypeError: target.hasOwnProperty is not a function
It is also impossible to merge such an object into immutable
:
var o1 = Immutable({a: 1});
var o2 = Object.create(null);
o2.b = 2;
o1.merge(o2);
// TypeError: other.hasOwnProperty is not a function
Obviously it depends on hasOwnProperty
method of the original object, which is usually available in a prototype chain.
Can we use global Object
methods instead, like Object.getOwnPropertyDescriptor
? I'll be happy to provide PR, but would like to discuss it first.
I noticed the compatibility list has IE9 and up, but does not list IE8. Unfortunately we have to support that. Has anyone actually tested seamless-immutable
in IE8?
I use moments (moment.js) rather then Date objects and they get lost in translation due to the cloning process. Does it make sense for there to be a way to tell Immutable how to handle additional object types?
I ran the first code example in a Node 4.1.1 console and everything that should not have mutated did. What could I be missing?
var Immutable = require('seamless-immutable');
var array = Immutable(["totally", "immutable", {hammer: "Can’t Touch This"}]);
array[1] = "I'm going to mutate you!"
array[1] // "I'm going to mutate you!"
array[2].hammer = "hm, surely I can mutate this nested object..."
array[2].hammer // "hm, surely I can mutate this nested object..."
for (var index in array) { console.log(array[index]); }
// "totally"
// "I'm going to mutate you!"
// { hammer: "hm, surely I can mutate this nested object..." }
JSON.stringify(array) // '["totally","I'm going to mutate you!",{"hammer":"hm, surely I can mutate this nested object..."}]'
Is there a way to validate that an array is an immutable array? Like something that would be instanceof for immutable arrays but not for normal mutable arrays.
The use case is actually React.Proptypes validation. Something similar to
propTypes: {
myobject: React.PropTypes.instanceOf(Immutable.List)
}
I know I can use a custom function in ProtoTypes and use Immutable.isImmutable, but adding a function like that in every component that should receive immutable prop objects, will not be pretty.
Here's current behavior as of 1.3.0:
obj = Immutable( {a: [1,2,3]} )
obj1 = obj.merge( {a: Immutable([1,2,3]) } )
obj2 = obj.merge( {a: [1,2,3] } )
obj === obj1 //false
obj === obj2 //false
obj3 = Immutable( {a: {b: 4} } )
obj4 = obj3.merge( {a: {b: 4} } )
obj5 = obj3.merge( Immutable({a: {b: 4} }) )
obj3 === obj4 //false
obj3 === obj5 //false
Is it possible to rename without
to omit
for lodash/underscore compatibility?
https://lodash.com/docs#without
https://lodash.com/docs#omit
Someone on Hacker News brought up benchmarks. It would be interesting to see how seamless-immutable
compares to other immutable libraries in js-hashtrie-benchmark (which seems like a fair comparison given API similarities, even though it does not happen to use a hashtrie implementation under the hood), particularly once Prod Mode exists.
If you call Immutable(obj)
on an obj
that contains a circular reference, you get a stack overflow as it tries to clone it.
I don't think supporting objects containing circular references is a good idea, but you should at least get a nice error message.
A heuristic could be useful here for performance, e.g. start by just keep count of how big the stack is getting; if it gets over (let's say) 1,000 frames, start over with circular reference detection.
I am not 100% sure what's happening as my research was limited. Still I'll try to explain the problem.
var constants = immutable({
BASE_PATH: '/my/files'
}),
BASE = constants.BASE_PATH,
menu = [
{
link: BASE,
title: 'Files'
},
{
link: `${BASE}/:sharing`,
title: 'Sharing'
}
];
...
render() {
var moreParams = ( this.props.params.splat || '' ).split('/'),
command = moreParams.length && moreParams[0].charAt(0) == ':' ? moreParams[0].substr(1) : null;
return (
<div className="_personal-storage">
<Menu items={ menu } />
<Storage base={ BASE } callback={ processCommand }>
<h1>Personal storage</h1>
</Storage>
</div>
);
}
Everything works this way, but as soon as I make menu
immutable, it goes to the weirdest infinite recursion in seamless-immutable.js:
Uncaught RangeError: Maximum call stack size exceeded
defineProperty @ (program):779
addPropertyTo @ seamless-immutable.js:5
makeImmutableObject @ seamless-immutable.js:342
Immutable @ seamless-immutable.js:371
Immutable @ seamless-immutable.js:367
Immutable @ seamless-immutable.js:367
Immutable @ seamless-immutable.js:367
makeImmutableArray @ seamless-immutable.js:106
Immutable @ seamless-immutable.js:354
Immutable @ seamless-immutable.js:367
The weirdest part is - it happens only if I pass menu
as a property to a react component. If I just log it to the console it works just fine. It doesn't matter if I make it immutable on declaration or later - it works the same.
When I've been debugging it, I couldn't believe my eyes - after making immutable my objects, it started making immutable React stuff {$$typeof: Symbol(react.element), key: null, ref: null, props: Object, _owner: ReactCompositeComponentWrapper…}$$typeof: Symbol(react.element)_owner: ReactCompositeComponentWrapper_self: null_source: null_store: Objectkey: nullprops: Objectref: nulltype: _default(props)__proto__: Object
and this is where the infinite recursion happens. But why does it make it immutable at all? I have no idea.
When using seamless-immutable, what is the recommended way of deriving new values from existing one (when map
and merge
aren't enough)? Typically, immutable data structures (IDS) have functions that create new IDS derived from them, such as Immutable.js' aList.push(1, 2)
or Clojure's updateIn
, assocIn
, dissoc
, etc. In seamless-immutable all array/object mutating functions just throw an error so to create a new IDS I suppose I have to do something like
var newArray; var tmp = Immutable([1,2,3]).asMutable(); tmp.push(4); newArray = Immutable(tmp);
Is that correct? Thank you!
It's really hard to use seamless-immutable without these methods. Any plans to implement them?
I'm attempting to use this in my angularjs app. It doesn't appear to be working. Any clue on how you'd implement it within Angular?
Add a deepEquals
to both ImmutableArray
and ImmutableObject
which skips checking any branches where the children are ===
and immutable.
When merging an immutable object with a mutable object, the resulting immutable object can contain mutable objects, but still identifies as immutable. For example, here's current behavior:
obj = Immutable( { key: 'value', collection: [ 1,2,3 ] } )
Immutable.isImmutable( obj ) //true
Immutable.isImmutable( obj['collection'] ) //true
newObj = obj.merge( { collection: [ 1,2,3 ] } )
Immutable.isImmutable( newObj ) //true
Immutable.isImmutable( newObj[ 'collection' ] ) //false
Immutable.isImmutable( newObj[ 'collection' ].push ) //danger!
its unclear what expected behavior should be. Personally, I think .merge should deeply convert everything that gets passed in to be immutable, as these partially immutable objects can create strange bugs. But maybe we should give users more flexibility. We could just add an isDeeplyImmutable function, and let users deal with the consequences.
I don't really understand why .map is wrapped to return an Immutable object. This seems needlessly restricting. In combination with #16, this also causes unexpected stack overflow errors.
Immutable([1,2,3]).map(function() {
return React.createElement( "div", null );
});
// too much recursion
Hi,
Thank you for that librairy, much easier to integrate in a React/Flux stack than immutable.js!
I want to use immutable variables to be able to not refresh my UI if the content of my objects doesn't change. In the shouldComponentUpdate
of my components, i want to test the strict equality between the old and the new prop. But it won't work as expected because:
> o = {a: [1, 2]}
{ a: [ 1, 2 ] }
> o2 = o
{ a: [ 1, 2 ] }
> o === o2
true
> i = Immutable(o)
{ a: [ 1, 2 ] }
> i2 = i.merge(o2)
{ a: [ 1, 2 ] }
> i === i2
false
I would like to read true
here.
The same test with an integer / string is working "as expected":
> o = {a: 'toto'}
{ a: 'toto' }
> o2 = {a: 'toto'}
{ a: 'toto' }
> i = Immutable(o)
{ a: 'toto' }
> i2 = i.merge(o2)
{ a: 'toto' }
> i === i2
true
My hopes are shattered!
But maybe I am missing something?
Thank you for your inputs.
I love seamless-immutable, but am missing a few methods such as immutablejs' update
and setIn
. Are there plans to add some? Another option would be to make seamless-immutable pluggable.
Currently, if you pass an object with a prototype to Immutable
, that prototype is discarded. It seems reasonable to preserve that prototype instead, with the understanding that not all of the methods it exposes will necessarily still work.
This would address #24
Thanks for this module!
Importing like this in es5: var I = require('seamless-immutable')
gives me:
{ [Function: Immutable]
isImmutable: [Function: isImmutable],
ImmutableError: [Function: ImmutableError] }
but I do not find a way to get the same thing in es6.
Doing import * as I from 'seamless-immutable'
gives:
{ isImmutable: [Function: isImmutable],
ImmutableError: [Function: ImmutableError],
default:
{ [Function: Immutable]
isImmutable: [Function: isImmutable],
ImmutableError: [Function: ImmutableError] } }
I can see that what I need is the default
inside but I do not manage to get it
Thank you!.
Just wondering before I test this out, I use cursors to great effect alongside the fynx immutable-flux library. I'd definitely want to test this library out as I am only targeting mobile platforms so I don't need old IE support.
Plans for cursors, compat with other cursor impls?
I'd like to be able to use this library to approximate Immutable.js Records: https://facebook.github.io/immutable-js/docs/#/Record
The benefits I see from this are helper functions (e.g. calculated fields on a data object- getName()
on an Immutable({firsName: ..., lastName: ...})
, defaulting fields, and validation.
From #25, I can see you've chosen not to support object prototypes by default, but I was wondering if there was a way to opt into its use. Looking at the code, I couldn't find a simple solution for this.
Do you have suggestions on how this type of functionality could be added, or potential hooks to enable the usecases above (defaulting might be the trickiest, but the others I feel could be handled by methods on the object)?
Thanks!
current behavior:
foo = Immutable({key: 'value'})
// Object {key: 'value'}
foo.merge('whoops')
// Object {0: "w", 1: "h", 2: "o", 3: "o", 4: "p", 5: "s", key: 'value'}
While this conforms with JavaScript strategy of coercing strings into objects, it leads to crazy errors. FWIW, lodash simply ignores strings that are passed to merge.
var immutable = require('seamless-immutable')
var c = immutable({a: {b: 1}})
console.log(c.merge({a: {b: 1}}, {deep: true}) === c) //returns false
The output should return true
but returns false instead.
console test:
var mydate = Immutable(new Date());
undefined
mydate
Fri Jun 12 2015 15:24:24 GMT+1000 (AEST)
mydate.setYear(2020)
1591939464409
mydate
Fri Jun 12 2020 15:24:24 GMT+1000 (AEST)
Last entry shows Date object hasn't been made immutable.
After playing with this a bit I realized I really don't think objects with custom prototypes should get Immutable
ized, recursively or whatever. The most common thing I've run into is trying to pass in myImmutable.map(item => (<MyComponent {...item}/>))
as the children of a React component. map
stack overflows on trying to Immutable()
-ize the React elements. What are the use cases for trying to immutablize objects with custom prototypes? Even Immutable.js allows you to stick non-immutable things into an immutable type. I prefer this concept of shallow immutability.
Unless I'm doing something daft (always a strong possibility!). Given
var obj = Immutable({
streams: {
a: { name: "Conversations", channel: ["Facebook","Twitter"], loading: false},
b: { name: "@Mentions", channel: ["Twitter"], loading: false},
c: { name: "Facebook", channel: ["Facebook"], loading: false}
},
streamOrder: ["c", "a", "b"]
})
then running j.merge({streams: {a: { loading: true }}}, true)
produces
{
streams: {
a: { loading:true }
},
streamOrder: {0: "c", 1: "a", 2: "b"}
}
so instead of doing a deep merge it has thrown away all of the data under the streams
key! Have I misunderstood how the deep merge is meant to work?
It's also converted the data under streamOder from an Array to an Object, but I can live with that for the time being :-)
Immutable(["foo", "bar"]).flatMap(function(val, index) {
return "index is " + index + " aaaand its type is " + (typeof index);
});
// [ 'index is 0 aaaand its type is string', 'index is 1 aaaand its type is string' ]
The offending line of code: https://github.com/rtfeldman/seamless-immutable/blob/master/src/seamless-immutable.js#L124
Evidence suggests that second argument needs to be parseInt(key)
which is absolutely gobsmacking.
Accessing process.env.NODE_ENV
property incurs significant performance overhead. See facebook/react#812
I'm trying to use seamless-immutable in this jsbin but whenever this line is ran:
var array = Immutable(["foo", "foo2", "bar", "bar2", "baz", "baz2"]);
I get this error:
Uncaught TypeError: Cannot redefine property: [object Object]
And this stack trace:
addPropertyTo @ seamless-immutable.js:5
makeMethodReturnImmutable @ seamless-immutable.js:84
makeImmutableArray @ seamless-immutable.js:94
Immutable @ seamless-immutable.js:328
(anonymous function) @ nesomi.js:36
applyStr @ ember.debug.js:18054
sendEvent @ ember.debug.js:12373
exports.default.mixin.Mixin.create.trigger @ ember.debug.js:29705
superFunction @ ember.debug.js:13704
EmberObject.default.extend.trigger @ ember.debug.js:40271
superWrapper @ ember.debug.js:17586
Renderer.default.didInsertElement @ ember.debug.js:38923
Renderer_renderTree @ ember.debug.js:8480
scheduledRenderTree @ ember.debug.js:8506
Queue.invoke @ ember.debug.js:871
Queue.flush @ ember.debug.js:936
DeferredActionQueues.flush @ ember.debug.js:741
Backburner.end @ ember.debug.js:166
Backburner.run @ ember.debug.js:221
executeTimers @ ember.debug.js:603
(anonymous function) @ ember.debug.js:592
var test = Immutable({mydate: new Date()})
test.asMutable({deep:true});
result:
Uncaught TypeError: obj.asMutable is not a function
at asDeepMutable (http://localhost:8080/resource-bundles/bowerComponents.resource/seamless-immutable/seamless-immutable.js:215:20)
at Object.asMutableObject (http://localhost:8080/resource-bundles/bowerComponents.resource/seamless-immutable/seamless-immutable.js:304:31)
at Object.eval (eval at evaluate (unknown source), :1:6)
at Object.InjectedScript._evaluateOn (:895:55)
at Object.InjectedScript._evaluateAndWrap (:828:34)
at Object.InjectedScript.evaluateOnCallFrame (:954:21)
Very cool! I think it's frowned upon though to have functions that start with a capital letter that are not constructor functions.
Technically JS functions are also mutable objects. Is it worth going through the trouble of converting them to be properly immutable? (e.g. freezing them etc.)
I'm defaulting to "no" for the sake of conserving performance, as functions are typically not used to store data, but noting the decision here in case it merits revisiting later.
Example:
var Immutable = require('seamless-immutable');
var d = { date: new Date() };
console.log(d.date.toISOString());
var d = Immutable(d);
console.log(d.date.toISOString());
Console:
2015-03-18T03:17:29.895Z
/Users/matt/Playground/seamless-immutable/index.js:5
console.log(d.date.toISOString());
^
TypeError: undefined is not a function
at Object.<anonymous> (/Users/matt/Playground/seamless-immutable/index.js:5:20)
at Module._compile (module.js:460:26)
at Object.Module._extensions..js (module.js:478:10)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Function.Module.runMain (module.js:501:10)
at startup (node.js:129:16)
at node.js:814:3
I think it would be helpful if merge returned the same object when no changes were made. Here's what I mean:
obj = Immutable({ key: 'value' })
obj1 = obj.merge({ key: 'new value '})
obj2 = obj.merge({ key: 'value' }) //no meaningful change
// current behavior
obj === obj1 //false
obj === obj2 //false
// ideal behavior
obj === obj1 //false
obj === obj2 //true
Immutable.js provides this feature with their 'set' function, which I've found helpful in the past.
This allows users to run quick checks to see if anything has changed after a merge, which would, for instance, be useful in the context of react/flux. While the check can be done after the fact (using a deepEquals function or something like that), combining that logic with merge has obvious performance benefits.
Maybe both versions of merge should be included in the public API, e.g. 'merge' and 'safeMerge' -- but that may also be overkill. Interested to hear thoughts on this.
Like how immutable-js does it, but maybe it passes a mutable copy into the function?
Like this?
var item = Immutable({name: "bob"})
// new Item is immutable, obj is mutable
var newItem = item.mutate(function(obj) {
obj.name = "henry"
})
The rationale is that it's really handy to be able to make multiple changes to an object in one go, and get an immutable object back. I'd prefer to have that callback be an immutable function itself, but I can't think of a good way to set properties without breaking type safety (imagine trying to use your .merge
with flow).
Using instanceof
to check for arrays etc. can cause issues across frame boundaries. Each document has a different context so one array may not be an instanceof
another.
var frame = document.createElement("iframe");
frame.onload = function(e) {
console.log([] instanceof frame.contentWindow.Array); // false
}
document.body.appendChild(frame);
I have a use case where I would like to override the normal merge behaviour for specific types or conditions.
I was thinking that you could send a custom merger function as a parameter with the config to merge
. If that function returns undefined
the merge would proceed as normal. If it returns something else then that value will be used instead of the normal rules.
A simple example that combines arrays instead of replacing them:
var one = {
id: 3,
list: [1, 3, 5]
};
var two = {
id: 3,
list: [2, 4, 6]
};
var arrayConcatMerger = function(current, other) {
if (current instanceof Array && other instanceof Array) {
return current.concat(other);
}
return;
}
var result = one.merge(two, {merger: arrayConcatMerger});
And the result would be:
{
id: 3,
list: [1, 3, 5, 2, 4, 6]
};
This would allow you to do more complex stuff like merging objects in an array if there exists an object with the same id in both arrays and so on.
Is there a way to opt-out of the array merging?
Here's current behavior as of 1.3.0:
obj = Immutable( {a: [1,2,3]} )
obj1 = obj.merge( {a: Immutable([1,2,3]) } )
obj2 = obj.merge( {a: [1,2,3] } )
obj === obj1 //false
obj === obj2 //false
obj3 = Immutable( {a: {b: 4} } )
obj4 = obj3.merge( {a: {b: 4} } )
obj5 = obj3.merge( Immutable({a: {b: 4} }) )
obj3 === obj4 //false
obj3 === obj5 //false
In production, it's probably unnecessary to actually freeze objects and throw exceptions when mutating methods are invoked. Those are mainly necessary in development, but by the time the code hits production, if any invariants aren't being followed, you should already know about it.
With that in mind, having a way to turn off the freezing and overriding should speed up performance, particularly in Safari. Adding a quick flag to turn this on would be useful to that end.
any way to import seamless-immutable with systemjs?
Hello,
I wonder whether I should pick seamless-immutable or icepick. They both seem to be doing the same thing, providing utilities for "modifying" deeply frozen data structures. I can see some differences but know too little to be able to really compare them and decide. Could you be so kind and describe the advantages of seamless over icepick and vice versa? Thanks a lot!
PS: I'll ask at the other project as well
with Immutable.js I can do
Immutable.List.of(1,2,3).update(0, x => x + 1)
This is useful if I have view state that is a list of 10 elements and I want to update just one of them that the user acts on.
What is the best practice for accomplishing this with seamless-immutable?
I've moved some bits around in a local clone of the repo so that rather than throwing an exception when any of the banned mutable methods are called, a quick copy is created and is mutated then returned as immutable.
Most of this logic happens in a new function.
function proxyMutableMethod(obj, methodName) {
var methodRef = obj[methodName];
addPropertyTo(obj, methodName, function() {
var host = obj instanceof Array ? [] : {},
copy = quickCopy(obj, host);
methodRef.apply(copy, arguments);
return Immutable(copy);
});
}
Rather than calling banProperty
for each of the banned methods, proxyMutableMethod
is called instead.
Now aside from the obvious performance implications (all these operations become at least O(n)
*) is there a reason why this isn't already a feature of the library? I understand that the performance itself may be enough of a reason, but it makes it more friendly for people coming from Clojure or Immutable.js. Just want to know whether I'm missing something before I crack on with writing the tests and amending the docs so I can send a pull request.
* It wouldn't be too much work to add an interface for defining structural sharing overrides for some of the mutable methods though.
Hello,
perhaps it wasn't my brightest day :-) but I found it difficult to find out when the dev or prod modes are active under Node.js. It would be great clarify that in the docs.
I believe this is true (and exhaustive) but am not sure:
/*1.*/ var Immutable = require('seamless-immutable'); // => dev mode
/*2.*/ var Immutable = require('seamless-immutable/seamless-immutable.production.min'); // => prod mode
/*3.*/ var Immutable = require('seamless-immutable/seamless-immutable.production.min'); process.env.NODE_ENV = "development"; // => dev mode
Thank you!
What do you think about making push
immutable operation (returning new extended array) instead of blocking it? The drawback is that you change conventional (but broken) API, but the benefit is that people could stop to use concat
where push
will satisfy. My arguments:
push
return value (updated array length 😞) right now so this API "change" shouldn't surprise or hurt.Array.prototype
so this is safe.flatMap
–push
then.Hi,
I love the fact that this library is super small and that doesn't try to do anything else than making your data immutable :).
There's only one thing holding me back from using it though:
Immutable([3, 1, 4]).sort()
// This will throw an ImmutableError, because sort() is a mutating method.
Have you considered returning a new immutable structure with the sorted array instead? (the same applies for every other banned method).
Thanks :)
Darío
I did this a few days ago in a fork:
crudh@8552c98
mergeDeep is like the normal merge function but it merges all children objects too. Right now there is no code reuse from the normal merge (except for 2 lines it is a copy and paste of merge) and the tests are simple copies from the normal merge.
Before cleaning it up for my fork I just wanted to know if there is any interest to pull it back into master? If so, are there any preferences for naming, code reuse and so on?
Scenario
Recieved a huge JSON response from an API request.
Problem
While setting the response as a JS object, into an immutable store via merge
— the function converts the huge object to an Immutable recurrsively. This is causes performance issues and makes the rendering slow.
Ideally if the user has set {deep: false}
at the time of merging, it should not try to convert recursively.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.