javascriptreactjsmobxautorun

MobX autorun and reaction inside constructor


Do autorun and reaction have to be inside the constructor in order to work? Can I write this simple example without constructor?

Also the code that I have inside the autorun runs normally but if I change it to console.log(this.expenses) it doesn't work. Why is that?

import { observable, action, computed, useStrict, autorun, reaction } from 'mobx'
useStrict(true)

class ExpensesStore {

  @observable.shallow expenses = []

  @action addExpense = (expense) => {
   this.expenses.push(expense)
  }

  @computed get getExpense() {
    if(this.expenses.length > 0) {
      return `This is computed from ${this.expenses[0] + this.expenses[1]}`
    }

  }


  constructor() {
    autorun(() => {
      console.log(`${this.expenses}`)
    })

    reaction(
      ()=>this.expenses.map(expense => expense), expense => console.log(expense)
    )
  }


}

const store = window.store= new ExpensesStore()

export default store

Solution

  • autorun and reaction does not have to be in the constructor. You could for example do this if you prefer:

    Example

    class ExpensesStore {
      @observable.shallow expenses = []
    
      @action addExpense = (expense) => {
       this.expenses.push(expense)
      }
    
      @computed get getExpense() {
        if(this.expenses.length > 0) {
          return `This is computed from ${this.expenses[0] + this.expenses[1]}`
        }
      }
    }
    
    const store = new ExpensesStore()
    
    autorun(() => {
      console.log(`${store.expenses}`)
    })
    
    reaction(
      () => store.expenses.map(expense => expense), expense => console.log(expense)
    )
    

    The reason why console.log(`${this.expenses}`) works and console.log(this.expenses) doesn't is because you are not dereferencing anything in your shallow array when you write this.expenses.

    When you write...

    `${this.expenses}`
    

    ... you are implicitly calling toString() on this.expenses. You can use toJS or slice to get the same effect when not putting it in a string:

    Example (JSBin)

    class ExpensesStore {
      @observable.shallow expenses = []
    
      @action addExpense = (expense) => {
       this.expenses.push(expense)
      }
    
      @computed get getExpense() {
        if(this.expenses.length > 0) {
          return `This is computed from ${this.expenses[0] + this.expenses[1]}`
        }
      }
    }
    
    const store = new ExpensesStore()
    
    autorun(() => {
      // store.expenses.slice() works just as well
      console.log(toJS(store.expenses))
    })
    
    reaction(
      () => store.expenses.map(expense => expense), expense => console.log(expense)
    )
    
    setTimeout(() => {
      store.addExpense(1000)
    }, 1000)