0

I started using MobX recently, and I came across the reaction concept. I understand reactions as functions, that runs side-effects. It is up to me, what side-effects are. I use reaction to update my MobX state. If you check my code below, I have a state for items (ingredients). What I want to do, is load ingredients from my localStorage (function getIngredients) and display them in my React. So far so good. Then, whenever I update ingredient (change name, price, weight - it is editable by form), I want to store this change in localStorage (function putIngredient), and then update my @observable ingredients accordingly (so later, when I get rid of localStorage and replace it with database, I keep track of my changes in MobX). I thought using MobX reaction is pretty nice idea how to handle it, but when I tried to run updateIngredients function, I got an following error:

Encountered an uncaught exception that was thrown by a reaction or observer component, in: 'Reaction[Reaction@1]' Error: "ingredients" is read-only

You can see, that inside updateIngredients function, there is one line commented. If I uncomment this line and comment the previous one (ingredients = ingredients.map(i => i.id === ingredient.id ? ingredient : i);), script will work. I thought, I can edit observable variables simply by reassigning them new values. Actually, I already did it like that in getIngredients function, where getIngredients function returns new array. So what is the trick here? Why I am getting this error?

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

import { getIngredients, putIngredient } from './localStorage';

class Ingredients {
  @observable ingredients = [];
  @observable updatedIngredient = null;

  @action.bound getIngredients(opts = {}) {
    this.ingredients = getIngredients({ ...opts });
  }

  @action.bound async putIngredient(ingredient) {
    putIngredient(ingredient);
    this.updatedIngredient = ingredient;
  }

  @action.bound updateIngredients(ingredient) {
    const { ingredients } = this;
    ingredients = ingredients.map(i => i.id === ingredient.id ? ingredient : i);
    // ingredients.replace(ingredients.map(i => i.id === ingredient.id ? ingredient : i));
  }
}

const IngredientsStore = new Ingredients();

reaction(
  () => IngredientsStore.updatedIngredient,
  (updatedIngredient) => {
    if (updatedIngredient) {
      IngredientsStore.updateIngredients(updatedIngredient);
    }
  }
)

export default IngredientsStore;
exoslav
  • 2,094
  • 3
  • 21
  • 26
  • What I figured out right now. If I dont use `{ ingredients } = this` in `updateIngredients` function and use `this.ingredients = this.ingredients.map(...)` script is working as expected. – exoslav Mar 05 '20 at 16:47

1 Answers1

0

You dereferenced value too early.

Basically you created some local variable ingredients which points to observable object and then you reassigned this local variable with another value. But this local variable is not observable so nothing changed for MobX. At the same time this.ingredients is observable property and MobX track access and changes to it.

More info here: https://mobx.js.org/best/react.html

Danila
  • 15,606
  • 2
  • 35
  • 67