Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to observe deep changes? #214

Closed
AriaFallah opened this issue Apr 24, 2016 · 21 comments
Closed

How to observe deep changes? #214

AriaFallah opened this issue Apr 24, 2016 · 21 comments

Comments

@AriaFallah
Copy link

AriaFallah commented Apr 24, 2016

Currently if you do

const myMap = map({ test: [] })
observe(myMap, () => console.log(test[0]))
myMap.get('test').push(1)

The console.log will not be triggered.

@AriaFallah AriaFallah changed the title Make observing a map deeply observe its properties. Allow observing a map to deeply observe its properties. Apr 24, 2016
@AriaFallah
Copy link
Author

AriaFallah commented Apr 24, 2016

Nevermind found a workaround

@AriaFallah AriaFallah changed the title Allow observing a map to deeply observe its properties. How to observe deep changes? Jun 5, 2016
@AriaFallah
Copy link
Author

AriaFallah commented Jun 5, 2016

@mweststrate

Any opinions on how to implement deep observation? Would like to implement it to achieve deep undo/redo functionality. Currently the solution I'm thinking of is manually calling observe on every single thing pushed into the map/array and then propagating that up to the root level. However that would have to happen for every level of nesting, and incurs the additional responsibility of having to dispose of each observer when the data is removed.

Is there any chance you'll be adding a deep version of observe that will also get the change events from anything stored inside of it?

@mweststrate
Copy link
Member

@AriaFallah I currently have no plans to introduce a deep observe. But I think you can achieve the same thing with a construction like reaction(() => walkTree(data), newTree => sideEffect) ? Or if you want an efficienter version, you can use createTransformer as well (see the docs).

@AriaFallah
Copy link
Author

How would you get the change event from doing that though?

@mweststrate
Copy link
Member

I think such a deep observe should be an abstraction over observe, maybe in a separate package. Because to be able to use such a feature you have to introduce some constraints on the object graph; probably you need it to be an object tree and have a consistent location, otherwise you cannot reliably determine the target?

@AriaFallah
Copy link
Author

Fair enough, perhaps now I'll consider taking the redux approach for deep undo/redo and store the whole entire object on each change. Thank you.

@superplussed
Copy link

Could we keep this issue open until there is a good solution /pattern found and we've updated the docs? It seems like this will be a common issue and warrants more discussion?

@AriaFallah
Copy link
Author

@superplussed

Agreed. Currently it's hard to do because it would mean that you'd have to do something like using observe for every single observable element recursively, and then on top of that have a way to dispose of the listeners when those elements are removed for example from an observable array.

The only way I could imagine achieving something like this would be to extend the current data structures to add the recursive observation, add a separate data structure to hold the functions that unregister the listeners created by observe(), and then add an ugly .delete() API or something for when you remove from your data structures that will also unregister the listener for that element. Otherwise, you'd end up with orphaned listeners.

That's the only way I've thought of doing it, but I'll reopen since I'm hoping there are perhaps better and more efficient ways to do it.

@mweststrate
Copy link
Member

@AriaFallah I'm thinking about a feature like this. Under what circumstances should a deep observe be able to work? Should it support only trees or also graphs? Only plain objects or also typed objects? And what events should it fire, something like the current events, that state the changed object but not where that object in the tree is, or something like json patches? See http://jsonpatch.com/

@AriaFallah
Copy link
Author

@mweststrate

I'll have to think about it more, but to be honest, even without my input, I'd trust your judgement on the implementation. I'll get back to you after really examining what I want out of this and what potentially other people want too.

@andrewluetgers
Copy link

@AriaFallah in my projects I have done deep observation via diffing new and old state objects. This way it is very efficient to store only the differences between state changes. These diffs are very quick because you never have to diff the whole state tree as the diff is always triggered by the setter that makes the state change in the first place. The state can then be 'played back' to perform a redo by taking the initial snapshot and applying all the changes in order. Undo by playing back from the beginning up to the point where you want to go back to. An optimization is to make a snapshot and playback from that point rather than the beginning. If you're interested this is implemented here, I have not done much with it lately but let me know if you have any use of it.

@AriaFallah
Copy link
Author

AriaFallah commented Jul 26, 2016

@andrewluetgers

Oh wow. I actually managed to do deep observation by using spy to do exactly what you described in this PR AriaFallah/mobx-store#35. Yours is on a whole other level since it doesn't crutch on MobX.

@andrewluetgers
Copy link

Yeah it's funny I learned of MobX and stopped working on this project because it performed just as well on my perf tests and the programming model is so simple and elegant.

@mweststrate
Copy link
Member

See also #374

This won't be fixed within mobx, for the simple reason that for clear semantics a deep observer should operate on trees, and not on arbitrarily object graphs. Stay tuned though, I'm thinking about some abstraction on top of mobx that have this restriction allowing this kind of functionality.

@epozsh
Copy link

epozsh commented Apr 19, 2017

Hello, one year later is there good solution for deep changes?
i need it on forms.
My form is like this in data submit:

const initial = {
    name "",
    surname: "",
    adresses: new Array()  <- this give me  warning when array empty *
}

*[mobx.array] Attempt to read an array index (0) that is out of bounds (0). Please check length first. Out of bound indices will not be tracked by MobX

my adresses is array of objects in which i have got some standard properties and i can add new one property, using extendObservable
adresses type of {state:'',town:'' }

extendObservable(this.props.data['adresses'][i], { [keyName]: '' });

my problem is that this to work i have to trigger for once one of property in adresses[i] by changing value then the input new appear which is observable. How make it appear instantly when i add new property to adresses?

@mweststrate
Copy link
Member

Mobx by default tracks deeply. It's just that the observe method specifically doesn't do that, but you are probably aren't using that. The warning you get just means that somewhere you are reading initial.address[0] before checking whether length is at least one.

Also note that adding properties with extendObservable won't be tracked. Either declare your properties up front with empty values, or use maps if you need dynamically keyed objects.

See also https://mobx.js.org/best/pitfalls.html

@epozsh
Copy link

epozsh commented Apr 19, 2017

Thanks for answering, i am new with mobx so i try hard to understand what am i missing my store is this (typescript):

const initial = {
   namel: "",
    surname: "",
    addresses: new Array()
}

export class formStore {
    constructor() {
        this.formData = initial
    }
    @observable formData: {
        [index: string]: any,
        name: string,
        surname: string,
        addresses: any[]
    };
    @action updatePropertyForm(key: string, value: any) {
        this.formData[key] = value;
    }

    @action updatePropertyAddresses(key: string, value: string, i: number) {
        this.formData.addresses[i][key] = value;
    }

    @action initializeFormData(data: any) {
        this.formData = data;
    }
    
     @action addEmptyAddress() {
        const emptyAddress = {
            state: "",
            townl: "",
        }
        this.formData['addresses'].push(emptyAddress);
    }

    @action addPropertyToAddresses(_keyName: string) {
        const length = this.formData['addresses'].length;
        for (var i = 0; i < length; i++) {
            let keyName = _keyName
            keyName = keyName.replace(/\s/g, '')
            keyName = toLowerChar(keyName);
            extendObservable(this.formData['addresses'][i], { [keyName]: '' });
        }
    }    
    
    @action removePropetyFromAddresses(_keyName: string) {
        const length = this.formData['addresses'].length;
            for (var i = 0; i < length; i++) {
                let keyName = _keyName
                delete this.formData['addresses'][i][keyName];
            }
    }
}

by calling add or remove i have to update one of other properties so can i see the new property i added.
I checked ur link but i do not find something to help me. How i can use map here? Sorry for inconvenience.

@mweststrate
Copy link
Member

@Shadowman4205:

@observable formData: {
        [index: string]: any,

MobX does not support dynamically keyed objects. Use observable maps instead.

@dagatsoin
Copy link

Hi, I don't know if someone is still interested by a deep observer for mobx but I build something like that here: https://github.com/dagatsoin/mobx-deep-observer
It is still an alpha version and would need some testers.

@rclai
Copy link

rclai commented Jun 21, 2018

This probably works now in v5?

@mweststrate
Copy link
Member

mobx-utils now exposes a deepObserve utility: https://github.com/mobxjs/mobx-utils#deepobserve

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants