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

afterCreate is firing when a computed accesses a child causing a side effect #967

Closed
mattruby opened this issue Aug 14, 2018 · 18 comments · Fixed by #1026
Closed

afterCreate is firing when a computed accesses a child causing a side effect #967

mattruby opened this issue Aug 14, 2018 · 18 comments · Fixed by #1026
Assignees
Labels
bug Confirmed bug has PR A Pull Request to fix the issue is available

Comments

@mattruby
Copy link
Member

FYI: I've posted this on spectrum. It looks like a possible bug.

Here's a small (50 ln) codesandbox example showing what I'm up against:
https://codesandbox.io/s/94zy6n96jo

My issue is with a tree where a child has an afterCreate action that modifies another child. If I access the child data using a computed I get the following error:
Error: [mobx] Computed values are not allowed to cause side effects by changing observables that are already being observed. Tried to modify: [email protected]

I assumed afterCreate would fire after the object had been created? Can anyone shed some light on when afterCreate and afterAttach really fire?
Thanks!!

@kristian-puccio
Copy link

pretty sure I'm having the same issue.
In my case I'm using mobx 4

@k-g-a
Copy link
Member

k-g-a commented Aug 15, 2018

Actually the error is "correct".
When you "pre-select" the first answer from Product's afterCreate - you modify observable selected from mobx action.
When you get "selectedAnswer" (which is mobx computed), during that get your Questions's afterCreate is called and it sets observable selected to another value.
It's like writing:

    get selectedAnswers() {
      return self.questions.reduce((result, question) => {
        if (question.answers.every(a => !a.selected))
          question.answers[0].selected = true
        result.push(question.answers.find(a => a.selected));
        return result;
      }, []);
    }

But I totally agree that such behaviour is not expected and quite unobvious.

This happens due to afterCreate is actually called during observalbe instance creation:

  • for roots it happens at the moment you call .create
  • for children (i.e. Product.questions or Question.answers / Question.answers[0] / Product.questions[1].answers[2] and so on) - on first access (from within the computed in this case).

First afterAttach happens right after afterCreate.
Subsiquent afterAttach'es happen when instance is moved to another parent within the same tree. Worths mentioning that detach operation extracts instance from tree, effectively setting it's parent to null, thus re-adding detached item will cause afterAttach to fire.

At the moment I can not figure out any way to fix this without breaking change (i.e. async afterCraete or introducing another hook), but i'll take time this week to dig deeper.

@luisherranz
Copy link
Member

Oh, so this is derived from the fact that starting from MST3 afterCreate doesn't fire until the instance is observed, right?

Maybe computed values should be allowed to fire afterCreates. After all, afterCreates should only fire once.

@mattruby
Copy link
Member Author

@k-g-a thanks for the great explanation! And thanks for looking into the issue. I'll try to work around this for the time being.

@k-g-a
Copy link
Member

k-g-a commented Aug 15, 2018

Oh, so this is derived from the fact that starting from MST3 afterCreate doesn't fire until the instance is observed, right?

Right. MST3 introduced lazy instances creation.

@kristian-puccio
Copy link

Not ideal but I managed to work around it by wrapping the call to the action (inside afterCreate) that does the mutations in a setTimeout. Seems to have fixed the errors

@mattruby
Copy link
Member Author

I ended up working around this by making explicit calls to preconfig actions after creation. Not great, but it will keep me moving.

@mweststrate
Copy link
Member

@k-g-a, mobx exposes an internal api, _allowStateChanges that could be used to explicitly allow these kind of side effects. I think we could use this probably in the lazy instantiation code to fix this problem so that this no longer breaks. Not sure yet if this also has potential downsides.

@xaviergonz
Copy link
Contributor

@mweststrate I tried to fix it using that but it will still throw, I think it is because of this:

if (globalState.computationDepth > 0 && hasObservers)
        fail(
            process.env.NODE_ENV !== "production" &&
                `Computed values are not allowed to cause side effects by changing observables that are already being observed. Tried to modify: ${
                    atom.name
                }`
        )

the check that throws the exception completely ignores globalState.allowStateChanges, which is what is set by _allowStateChanges

I guess that if would need to be changed inside mobx to

if (!globalState.allowStateChanges && globalState.computationDepth > 0 && hasObservers)

before it can be fixed?

@xaviergonz
Copy link
Contributor

or maybe creating a whole new globalState.allowStateChangesInsideComputed / _allowStateChangesInsideComputed to be on the safe side

@xaviergonz
Copy link
Contributor

just created a PR on mobx for it

@mweststrate
Copy link
Member

@xaviergonz ok sorry, should have read above comments before reading the PR on mobx, lol :). Ok, I'm going to check what the impact of that check would be, adding another global state modifier wrapping function doesn't make it easier to grasp :)

@xaviergonz xaviergonz added the bug Confirmed bug label Sep 14, 2018
@k-g-a
Copy link
Member

k-g-a commented Sep 24, 2018

@xaviergonz thanks for picking that up! Any update on this?

@xaviergonz
Copy link
Contributor

still waiting on the PR on the mobx side to resolve before this one can be addressed I'm afraid

mweststrate pushed a commit to mobxjs/mobx that referenced this issue Sep 25, 2018
…d value (#1706)

* make allowStateChanges allow changes inside computed value

in order to fix MST mobxjs/mobx-state-tree#967

* added allowStateChangeInsideComputed

* added unit test

* small unit test error

* safer implementation

* Alternative solution

* Revert "Alternative solution"

This reverts commit e15bc78.

* improve unit test

* Changed to more predictable restoration
mweststrate pushed a commit to mobxjs/mobx that referenced this issue Sep 25, 2018
…side a computed value (#1706)

* make allowStateChanges allow changes inside computed value

in order to fix MST mobxjs/mobx-state-tree#967

* added allowStateChangeInsideComputed

* added unit test

* small unit test error

* safer implementation

* Alternative solution

* Revert "Alternative solution"

This reverts commit e15bc78.

* improve unit test

* Changed to more predictable restoration
@mweststrate
Copy link
Member

@xaviergonz released! MobX 4.5.0 / 5.5.0 includes the change

@xaviergonz xaviergonz added the has PR A Pull Request to fix the issue is available label Sep 27, 2018
@xaviergonz
Copy link
Contributor

@mweststrate and there's the PR :)

@xaviergonz
Copy link
Contributor

should be fixed in 3.5.0, but only if mobx >=4.5.0 || >= 5.5.0 is installed as well

@mattruby
Copy link
Member Author

mattruby commented Sep 29, 2018 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Confirmed bug has PR A Pull Request to fix the issue is available
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants