Mithril.js: vnode.state of components does not use deep-cloning

Created on 14 Mar 2017  Â·  13Comments  Â·  Source: MithrilJS/mithril.js

I have created a component having an object as state-value, but this seems to be shared on ALL instances of that one component.

var SomeSpecialSubComponent = {
    someKey: "some Value which will be overwritten on oninit",
    someObjectWithKey: {
        subKey: "this will be shared across all instances"
    },
    oninit: function(vnode){
        // copy on instance creation
        vnode.state.someKey = vnode.attrs.someAttributeKey;
    },
    view: function(vnode){
        return [
            m("button", {
                onclick: function(){
                    // this will set the "state" of the current instance, but will change the state on ALL instances
                    vnode.state.someObjectWithKey.subKey = vnode.state.someKey;
                }
            }, "load current value"),
            m("div", vnode.state.someObjectWithKey.subKey)
        ];
    }
};

var someStupidArray = [
    "stupid value 1",
    "stupid value 2",
    "stupid value 3"
];

var SomeSpecialComponent = {
    view: function(vnode){
        return someStupidArray.map(function(someStupidValue){
            return m( SomeSpecialSubComponent, {
                someAttributeKey: someStupidValue
            });
        });
    }
};


m.mount( document.body, SomeSpecialComponent );

Is this expected behaviour or just some side-effect of not deep-copying state-fields on creation?

All 13 comments

Current workaround is to store blank object within oninit:

var SomeSpecialSubComponent = {
    // ...
    someObjectWithKey: {
        subKey: "this will be shared across all instances"
    },
    oninit: function(vnode){
        // copy on instance creation
        vnode.state.someKey = vnode.attrs.someAttributeKey;
        // overwrite
        vnode.state.someObjectWithKey = {
            subKey: "this is only valid to local instance"
        };
    },
    // ...
};

This is by design and could perhaps be made clearer in the documentation: the component definition is the prototype of each component instance, so the state isn't event shallow copied, it's accessed via prototypal inheritance.

The rule of thumb is that while you can set primitive initial values for state on the component definition (and functions, to a degree, although ensuring this and the right arguments are received can be difficult), any object you wish to be available per component instance should be defined in oninit. IMO if you have any objects fitting this requirement, it's better to simply declare and bind all initial state in oninit — that way you avoid rebinding / copying, and only have to look in one place to see what a component's initial state looks like.

Hm ... thanks for clarifying this ... and yes, this must be part of the documentation, because my naive assumption was that all additional fields are "for each component instance", but in the end it might be a way for sharing data across all instances. Is there a debug-mode where this could be pointed out as a warning?

Just a note why I use objects: I do not REQUIRE this, but I like to group my values and instead of ugly naming I just use objects for that.

Any property attached to the component object is copied for every instance of the component. This allows simple state initialization.

@barneycarroll this is an excerpt from the current documentation ... which means this statement is false, as it is NOT copied tho :(

I updated the documentation to correct this a couple of weeks ago, but the documentation hasn't been published since then. That paragraph now reads:

The component object is the prototype of each component instance, so any property defined on the component object will be accessible as a property of vnode.state. This allows simple state initialization.

As this is really easily overseen an explicit example with this shared-state thing should be done additionally. I often just "scan" the code inside documentation and do know that others not even take a glimpse at written text documentation ... so this might be a good chance for special reference?

I agree @FibreFoX, the implications aren't obvious. There should be an example at the bottom of how this can go wrong.

Personally I don't have anything against the current behaviour, it just caught me by surprise.

There was another instance of "shallow cloning" language in the vnode documentation page, that was fixed with #1695

I agree that an example may improve things.

Just some additional note: this "problem" should be stated for normal javascript objects AND javascript arrays too (which are just special objects), because for some developers (especially coming from different languages to javascript) this isn't that clear.

I'm developing without fancy ES 6 stuff with legacy "one javascript without NPM or other packaging-stuff", so maybe this is a bit "outdated" style for "frontend"-development.

@FibreFoX prototypes are a very complicated aspect of Javascript that have been here from the beginning. ES6 makes them appear less complicated (sometimes) but doesn't really change the fundamental mechanisms. It's impossible to give a general introduction to Javascript prototypes without a wall of text, but your advice to include examples of arrays and plain objects screwing up is a good starting point.

Closing as the original issue was resolved as by-design.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

josephys picture josephys  Â·  4Comments

StephanHoyer picture StephanHoyer  Â·  4Comments

tivac picture tivac  Â·  3Comments

simov picture simov  Â·  4Comments

pygy picture pygy  Â·  4Comments