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

requiresReaction in options of computed does not throw as indicated by the documentation #3618

Open
upsuper opened this issue Jan 30, 2023 · 7 comments
Assignees
Labels

Comments

@upsuper
Copy link
Contributor

upsuper commented Jan 30, 2023

import * as mobx from 'mobx';
const a = mobx.computed(() => console.log('b'), { requiresReaction: true });
mobx.onBecomeUnobserved(a, () => console.log('c'));
a.get();

The document says:

requiresReaction

It is recommended to set this one to true on very expensive computed values. If you try to read its value outside of the reactive context, in which case it might not be cached, it will cause the computed to throw instead of doing an expensive re-evalution.

Intended outcome:

It should throw, because it is not called within a reactive context.

Actual outcome:

It does not throw.

How to reproduce the issue:

Just run the code above.

Versions

MobX 6.

@upsuper
Copy link
Contributor Author

upsuper commented Jan 30, 2023

I noticed that this behavior was changed in #2998 possibly intentionally.

But as I mentioned in #2417 (comment) previously, we in Canva find it hard to enforce restrictions if they don't actually throw, because developers don't look at console all the time when nothing goes wrong, and it is hard to track all the requirements across a large codebase. So we would prefer there to be a way to enforce the restrictions.

If it is intended to make requiresReaction on par with behavior of other requires* to warn, it would be better to introduce a new enforcesReaction to actually throw.

@upsuper
Copy link
Contributor Author

upsuper commented Jan 30, 2023

There is also another potential use case of requiresReaction: computed may sometimes be used for managing resources, which may be leaking, on demand. To prevent leaking, we may use onBecomeUnobserved to clean up. However, it doesn't work outside observed context, so one may want to enforce reaction for that reason.

An example as below:

let _blobUrl: string | undefined;
const blobUrl = mobx.computed(() => {
  // some check
  if (_blobUrl != null) {
    URL.revokeObjectURL(_blobUrl);
  }
  // generate a blob
  _blobUrl = URL.createObjectURL(blob);
  return _blobUrl;
});
mobx.onBecomeUnobserved(blobUrl, () => {
  if (_blobUrl != null) {
    URL.revokeObjectURL(_blobUrl);
    _blobUrl = undefined;
  }
});

However, if you call blobUrl.get() outside observed context in a one-off way, the URL it generates will not revoked by onBecomeUnobserved as expected.

@urugator
Copy link
Collaborator

Do I follow correctly it actually reports, so it's only about warn vs throw?

As a workaround you can patch console.warn and use regex to intercept what's needed.

I would like to keep it consistent. I also prefer if errors are sound and basically non-ignorable, but one thing to consider is that these checks are dev only - conceptully I think it's a bit weird if something is considered an error in dev, but not on prod. IIRC React also just logs in most of the "suspicious" situations. Also errors are often thrown from async contexts, so they end up only in the console anyway.

Maybe we could introduce report/warn config option, which defaults to console.warn:
configure({ report: (msg) => { throw new Error(msg) } })
?

@mweststrate
Copy link
Member

There are many warnings that should be fixed, like React key warnings, that are not errors to not deviate from production behavior, as otherwise bug reproductions can become extremely hard. If checking warnings is not part of the developers typical habits before putting up a PR, I'd indeed solve this as the infra level of your product, and raise on-screen alerts on any logged warning/error you believe must always be fixed in your company.

That being said, I think this flag was actually intended to throw even in production, and that the bug here is that it was after the __DEV__ check originally, where it should have been before. The rationale behind a throw here was that it is not a warning about something that by some heuristic probably has been done wrong. Instead, the user explicitly expressed that for this specifically computed it is wrong.

So I'd be supportive of moving this back to throw, but move the check outside DEV. My concern however is potentially breaking production systems, so I'm wondering if this should be a major change? Advocating for the devil, the spec says already it throws, so it is probably fine as patch.

@upsuper
Copy link
Contributor Author

upsuper commented Feb 1, 2023

There are many warnings that should be fixed, like React key warnings, that are not errors to not deviate from production behavior, as otherwise bug reproductions can become extremely hard.

While it is true that lack of key in list is a React warning that should be fixed, but this mistake is generally very local, and can easily be caught during self-review and review, as well as via static linting.

However, it is not the case for things like enforceActions and requiresReaction. Errors on those are very remote, i.e. where they are required and where they happens are far from each other, many of the time not even in the same package. And with the help of proxy, some of the operations are implicit, making it even harder to recognize. So I don't think it's reasonable to put them together.

Also warnings don't always carry a stack (as in Firefox), and sometimes don't even carry the location (as in Safari), which can make it harder to locate the source of the issue given the remote natural of the issue.

If checking warnings is not part of the developers typical habits before putting up a PR, I'd indeed solve this as the infra level of your product, and raise on-screen alerts on any logged warning/error you believe must always be fixed in your company.

That is an interesting suggestion we will consider.

But as I mentioned above, other warnings are not generally an issue given their local nature in code, and we haven't been having problem with preventing them, so this issue is pretty much specific to the mobx ones.

@upsuper
Copy link
Contributor Author

upsuper commented Feb 1, 2023

I would also argue that

raise on-screen alerts on any logged warning/error you believe must always be fixed in your company.

is not a very sustainable way to do it in long term.

You already don't consider behavior change switching from throwing to warning to be a major change, how can we ensure that the warning message wouldn't get accidentally reworded in a way we fail to capture it after a patch upgrade, and without even the change log mentioning it given it's trivial looking?

@urugator
Copy link
Collaborator

urugator commented Feb 1, 2023

You already don't consider behavior change switching from throwing to warning to be a major change

The change itself wasn't breaking, it only changed the dev experience.
Obviously format of dev messages (not prod errors) is not something one should rely on, unless we would have designed it with such a goal in mind, because of a feedback like this (eg prefixing it with a code).

without even the change log mentioning it

https://github.com/mobxjs/mobx/releases/tag/mobx%406.3.3

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

No branches or pull requests

3 participants