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

RangeError when passing an observable HTMLElement to toJS #566

Closed
maxdeviant opened this issue Sep 19, 2016 · 13 comments
Closed

RangeError when passing an observable HTMLElement to toJS #566

maxdeviant opened this issue Sep 19, 2016 · 13 comments

Comments

@maxdeviant
Copy link
Member

When you pass an observable HTMLElement to toJS, a RangeError occurs:

mobx.js:427 Uncaught (in promise) RangeError: Maximum call stack size exceeded(…)
@maxdeviant
Copy link
Member Author

Working on a fix for this, but having a hard time writing tests, since there is no DOM in the test runner.

@andykog
Copy link
Member

andykog commented Sep 19, 2016

@maxdeviant, what do you mean by “observable HTMLElement”? Please, don't say that you are doing something like extendObservable(myDiv, {}) :-)

Working on a fix for this, but having a hard time writing tests, since there is no DOM in the test runner.

You can run it in chrome like this: npm run test-browser-chrome, but note that mobx should not be tight to DOM.

@andykog
Copy link
Member

andykog commented Sep 19, 2016

Strange, that this error occurs, it have to be prevented by __alreadySeen check.

@maxdeviant
Copy link
Member Author

@andykog Well, my original case was something like this:

class MyStore {
    @observable
    public images = asMap<HTMLImageElement[]>({
        123: new Image()
    });
}

// somewhere else in my code
console.log(toJS(myStoreInstance.images));
// throws the RangeError

What I am really interested in is when entries are added to/removed from the map, not necessarily observing the element itself.

@maxdeviant
Copy link
Member Author

maxdeviant commented Sep 19, 2016

note that mobx should not be tight to DOM.

I noticed that when I tried a simple instanceof HTMLElement check and it said HTMLElement was undefined 😅

It appears that there are ways to check for an HTML element without instanceof, like looking for certain properties, etc.

@andykog
Copy link
Member

andykog commented Sep 19, 2016

Well instanceof check can be made safely like this:

var checkIfDomNode = (typeof document === 'object' && document && document.createElement && typeof HTMLElement === 'function' && typeof Node === 'function')
  ? s => s instanceof Node
  : () => false;

@maxdeviant
Copy link
Member Author

maxdeviant commented Sep 19, 2016

Ah, good point. I had it done like this:

function isHTMLElement(source) {
    try {
        return source instanceof HTMLElement;
    } catch (err) {
        return (
            source !== null &&
            typeof source === "object" &&
            source.nodeType === 1 &&
            typeof source.style === "object" &&
            typeof source.ownerDocument === "object"
        );
    }
}

Also, is there a way to add a test that only runs when executed in a browser? It doesn't appear that process.env.BROWSER (or something equivalent) is being set anywhere when running the test suite.

@andykog
Copy link
Member

andykog commented Sep 19, 2016

My approach is a bit more performant :-)

Also, is there a way to add a test that only runs when executed in a browser? It doesn't appear that NODE_ENV is being set anywhere when running the test suite.

Nope, but you can detect DOM the same way:

t.test('...', function(t) {
 if (typeof document === 'object') {
   toJS(document.createElement('img'));
 }
 t.end();
} 

@mweststrate
Copy link
Member

I'm not entirely sure about this PR;

  1. as @andykog indicated, the alreadySeen mechanism should prevent this behavior, maybe there is a bug in there
  2. this fix is not really scalable, generic, as there is potentially an unlimited amount of concepts in JavaScript that should not be serialized (for example: Promises)
  3. This makes me wonder whether the current behavior of toJS is actually desired. Probably it should only process plain objects / arrays and collections known to MobX, but not other objects that have a prototype? (Or maybe just their observable properties and nothing more)

@andykog
Copy link
Member

andykog commented Sep 23, 2016

@mweststrate, this bothers me in current toJS behaviour as well, but to change it we have to at least somehow mark classes with properties decorated with @observable

@mweststrate
Copy link
Member

@andykog they return true in isObservableObject. From there on we could iterate the observable, non computed properties as stored in the $mobx administration object

@maxdeviant
Copy link
Member Author

@mweststrate You are correct in that my PR is not really generic, since it grew out of an issue that we were encountering in our codebase.

So if there is a better solution that can be achieved that fixes the issue, I am all for that.

@mweststrate
Copy link
Member

Closing this issue as it has been solved in 2.7.0. Feel free to re-open if this isn't the case.

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

Successfully merging a pull request may close this issue.

3 participants