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

Initializing observables in constructor. #563

Closed
jeffijoe opened this issue Sep 19, 2016 · 21 comments
Closed

Initializing observables in constructor. #563

jeffijoe opened this issue Sep 19, 2016 · 21 comments
Assignees

Comments

@jeffijoe
Copy link
Contributor

jeffijoe commented Sep 19, 2016

I've got a use case where I could use some advice as to how I should go about this "the MobX way".

Note: This is not even close to my actual code, but the problem is there

mobx.useStrict(true)

class Model {
  @observable name
  @observable email
}

class ViewModel {
  @observable name
  @observable email

  constructor (model) {
    this.model = model
    this.name = model.name
    this.email = model.email
  }

  save () {
    this.model.name = this.name
    this.model.email = this.email
  }
}

class Editor {
  @observable models = []
  transformer = createTransformer((model) => new ViewModel(model))

  @computed get viewModels () {
    return this.models.map(this.transformer)
  }
}

const model = new Model()
model.name = 'Jeff'
model.email = '[email protected]'

var editor = new Editor()
editor.models.push(model)

The problem arises when the transformer is invoked; I am not allowed to initialize the viewmodel state. I know I could just write getters and setters, but that's too much boilerplate, and the viewmodel has more complicated state as well.

Whats the best way to approach this without breaking the MobX guidelines? Or is there an escape hatch for the cases where I know what I am doing? 😄

Question is if I can allow state changes for observables that were created during the current "execution"? I would never modify existing state, but I should be able to initialize new state, right?

@andykog
Copy link
Member

andykog commented Sep 19, 2016

@jeffijoe, you can rewrite ViewModel class like this:

class ViewModel {
  constructor (model) {
    const { name, email } = model;
    extendObservable(this, { model, name, email });
  }

  save () {
    this.model.name = this.name
    this.model.email = this.email
  }
}

@jeffijoe
Copy link
Contributor Author

@andykog ah, because that does the init?

That's not a very pretty solution though, as I would like to continue using decorators with actual default values @observable name = ''

@mweststrate
Copy link
Member

I think

  constructor (model) {
    mobx.extras.allowStateChanges(true, () => {
      this.model = model
      this.name = model.name
      this.email = model.email
    })
  }

Should work. Maybe a more neat solution is to allow state changes to observables that have no observers (and maybe also: that where created in the current tick)

@jeffijoe
Copy link
Contributor Author

@mweststrate thanks for the workaround :)

I think it makes sense to allow changes for observables created in the current tick as well. :)

@antitoxic
Copy link

allow state changes to observables that have no observers (and maybe also: that where created in the current tick)

Definitely makes sense. If I could vote, I would vote for that :)

@devdoomari
Copy link

devdoomari commented Oct 31, 2016

@mweststrate are there plans to make @usestrict as a decorator (for single-use ?)

@useStrict(true)
class PostsStore {
   @observable posts: Array<Post> = [ ];
   ...
   @action addPost( ... ) { ...
}

@mweststrate
Copy link
Member

@devdoomari no, but mobx-state-tree will offer a similar concept; it's action will only allow to change the owning object (or descendants)

@mweststrate
Copy link
Member

If there is a champion willing to implement these two features, he is more then welcome!

  1. Maybe a more neat solution is to allow state changes to observables that have no observers (and maybe also: that where created in the current tick)

  2. I think it makes sense to allow changes for observables created in the current tick as well. :)

I think the first one should be quite easy, for the second one, no clue yet, maybe that isn't easily, cleanly doable :)

cc: @capaj @urugator @antitoxic @jeffijoe @asterius1 @andykog @delaetthomas

@antitoxic
Copy link

I will have to miss this one. Life is pushing stuff :) new job, flat and so on.

A mobx internals crash course would be useful if I have to dig in at some point :)

@mweststrate mweststrate self-assigned this Feb 1, 2017
@mweststrate
Copy link
Member

See also #798.

@mweststrate
Copy link
Member

Proposal to change the state modification guard to the following behavior:

Changing a piece of state is only allowed if any of the following conditions are met:

  • (!strict mode || in action) && (!in computed || no observers) (supports return new observables from a derivation
  • observable created in same tick && no observers (support creating and initializing observables in constructors, without marking constructor as @action (which is not possible)

For reference, the current behavior of the guard is: (!strict mode || in action) && (!in computed)

@mweststrate
Copy link
Member

Just released 3.1.0, in which constructors no longer are required to wrap their assignment in actions

@stereokai
Copy link

@mweststrate What's up :)

So does that mean all constructors are in fact implicit actions?

@urugator
Copy link
Collaborator

So does that mean all constructors are in fact implicit actions?

No, in actions you can modify any observable, in constructors (or generally anywhere outside of action) you can modify only unobserved observables.

@stereokai
Copy link

Well, when I'm modifying an observable value from a constructor of the class it belongs to, MobX tracks and propagates the change to observers.

@brunolm
Copy link

brunolm commented Jun 22, 2018

I think I'm having the same issue, but on Angular service

[mobx] Since strict-mode is enabled, changing observed observable values outside actions is not allowed.

@urugator
Copy link
Collaborator

@brunolm Is #1507 (comment) relevant?

@brunolm
Copy link

brunolm commented Jun 22, 2018

@urugator if I set to true it seems to have no effect, from a component I can get the service

import { Component } from '@angular/core';

import { AppStoreService } from '../app-store.service';

@Component({
  selector: 'app-about',
  templateUrl: './about.component.html',
  styleUrls: ['./about.component.scss'],
})
export class AboutComponent {
  constructor(
    public store: AppStoreService,
  ) {}


  updateTitle() {
    // when strict --- this is blocked
    // when true --- should not be allowed, but is
    this.store.about.title = 'some random string'; 

    // action to change title
    // this.store.about.randomTitle();
  }
}

@urugator
Copy link
Collaborator

urugator commented Jun 22, 2018

@brunolm Is this.store.about.title observed by something? component/autorun/reaction? If not, it's not supposed to throw...
"strict" throws anywhere outside of action including prop initializers, there is no way to detect whether an assigment is or isn't prop initializer

@brunolm
Copy link

brunolm commented Jun 22, 2018

@urugator hmm I see, so what I want is strict, I was able to workaround it by calling an @action in the constructor, that works for me

import { Injectable } from '@angular/core';
import { action, observable } from 'mobx-angular';

@Injectable({
  providedIn: 'root',
})
export class AboutStoreService {
  @observable title: string;

  constructor() {
    this.initialize();
  }

  @action
  private initialize() {
    this.title = 'about:hello world';
  }

  @action
  randomTitle() {
    // tslint:disable-next-line:no-magic-numbers
    this.title = Math.random().toString(32);
  }
}

@lyzhovnik
Copy link

Also you can do initializing inside runInAction functions, like:

import { observable, action, runInAction } from 'mobx';

class Steps<StepsType> {
  @observable
  public activeStep: StepsType;

  constructor(activeStep: StepsType) {
    runInAction(() => {
      this.activeStep = activeStep;
    });
  }

  @action
  public goToStep = (step: StepsType) => {
    this.activeStep = step;
  };
}

export default Steps;

@mobxjs mobxjs locked as resolved and limited conversation to collaborators Jul 31, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

9 participants