Categories
Mobx

Adding Computed Values to MobX Observable Classes

In this article, we’ll look at how to use the computed decorator to derive computed values from existing states.

computed Decorator

Computed properties are derived from other Observable properties of a class. It won’t be computed again if none of the Observable values that derive the computed property has changed.

Computed properties aren’t enumerable, nor can they be overwritten in an inheritance chain.

To create a computed property, we can write the following:

import { observable, computed } from "mobx";

class Person {
  @observable firstName = "Jane";
  @observable lastName = "Smith";

  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  @computed get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

In the code above, the @computed decorator decorates the fullName getter property, which returns the combination of firstName and lastName .

We can also use the decorate function to create computed properties as follows:

import { decorate, observable, computed } from "mobx";

class Person {
  firstName = "Jane";
  lastName = "Smith";

  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

decorate(Person, {
  firstName: observable,
  lastName: observable,
  fullName: computed
});

The code above is the same as using decorators.

We can also define getters for objects:

import { observable } from "mobx";

const person = observable.object({
  firstName: "Jane",
  lastName: "Smith",
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
});

Then fullName will be watched like any other Observable object properties.

We can also define setters as follows:

import { observable, computed } from "mobx";
class Person {
  @observable firstName = "Jane";
  @observable lastName = "Smith";
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  @computed get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  set fn(value) {
    this.firstName = value;
  }
}

In the code above, the fn method is our setter function for setting the firstName property.

computed(expression) as a Function

MobX also comes with a computed function which takes a callback that returns the value we want.

For example, we can use it as follows:

import { observable, computed } from "mobx";
const name = observable.box("Jane");
const lowerCaseName = computed(() => name.get().toLowerCase());
const disposer = lowerCaseName.observe(change => console.log(change.newValue));
name.set("Joe");

In the code above, we created the name Observable string with the initial value 'Jane' and then we created a computed value called lowerCaseName .

In the callback we passed into the computed function, we return the name ‘s value converted to lower case.

Then we attached a listener to watch for changes in lowerCaseName .

When we set the value of name , the callback in computed will run and then the change listener for lowerCaseName also runs as the value changes.

Options for computed

computed takes a second argument with various options that we can set to change the computed value’s behavior.

They’re the following:

  • name — a name we set and used for debugging
  • context — the this value that should be used in the provided expression
  • set — the setter runction to be used. We need this to assign new values to a computed value
  • equals — a comparison function for comparing the previous value with the new value. Defaults to comparer.default .
  • requiresAction — we should set this to true on very expensive computed values. If we try to read its value but the value isn’t tracked by some observer, then the computed property will throw an error instead of doing a re-evaluation
  • keepAlive — don’t suspend the computed value if it’s not observed by anything. This can easily lead to memory leaks.

Built-in comparers

MobX has the following built-in comparer s:

  • comparer.identity — uses the === operator to determine if 2 values are the same
  • comparer.default — same as comparer.identity , by considers NaN to be equal to NaN .
  • comparer.structural — performs a deep structural comparison to determine if 2 values are the same
  • comparer.shallow — perfoms shallow structural comparison to determine if 2 values are the same

Error Handling

If a computed value throws an exception during its computation, it’ll be caught and rethrown any time its value is read. To preserve the original stack trace, we should use throw new Error('...') .

For example, we can write the following:

import { observable, computed } from "mobx";
const x = observable.box(3);
const y = observable.box(1);
const divided = computed(() => {
  if (y.get() === 0) {
    throw new Error("Division by zero");
  }

  if (isNaN(y.get())) {
    throw new Error("Not a number");
  }
  return x.get() / y.get();
});

divided.get();

y.set(0);
divided.get();

y.set(2);
divided.get();

We throw errors in the computed function’s callback.

Computed Values with Arguments

We can use the mobx-utils package’s computedFn function to create a computed function that takes an argument.

For example, we can use it as follows:

import { observable } from "mobx";
import { computedFn } from "mobx-utils";

class Todos {
  @observable todos = [];

  getAllTodosByName = computedFn(function getAllTodosByName(name) {
    return this.todos.filter(todo => todo === name);
  });
}

const todos = new Todos();
todos.todos.push("eat");
console.log(todos.getAllTodosByName("eat"));

In the code above, we have:

getAllTodosByName = computedFn(function getAllTodosByName(name) {
  return this.todos.filter(todo => todo === name);
});

which creates a getAllTodosByName method that can be called and get the returned value like any other method.

The last console.log line will get us the value. We have to use a traditional function as the computedFn callback so that we get the correct value for this .

Conclusion

We can use the computed decorator to create computed values in classes.

We can add setters to set the value of a computed value.

The computed function takes a callback that returns an Observable entity that we can watch and manipulate. It takes a callback that returns the value that we want to derive from an existing Observable value.

The computedFn function takes a callback to return the value we want and returns a function that we can call to get that value.

Categories
Mobx

Creating and Using Mobx Observable Maps

Mobx is a state management solution for JavaScript apps. It lets us observe values from a store and then set the values, which will be immediately reflected in the store.

In this article, we’ll look at how to create and use Observable Maps with Mobx.

Observable Maps

Observable maps are a collection of key-value pairs that we can get and set. The value changes can be watched.

To create an Observable map, we can use the observable.map method as follows:

import { observable, autorun } from "mobx";

const values = {
  foo: 1,
  bar: 2
};

const observableMap = observable.map(values);

autorun(() => console.log(observableMap.toJS()));

In the code above, we defined an Observable map by creating an values object and then passing in values into the observable.map method.

It also optionally takes a second argument to specify various options.

We can also pass in an ES6 JavaScript Map instance as follows:

import { observable, autorun } from "mobx";

const values = new Map([["foo, 1"], ["bar", 2]]);
const observableMap = observable.map(values);
autorun(() => console.log(observableMap.toJS()));

In both examples, the autorun function watches the value of the Observable map as it changes.

It’ll log the value when it’s created in both examples.

A Mobx Observable map has the same methods as an ES6 Map and more. They’re the following:

  • has(key) — returns whether a map has an entey with the provided key.
  • set(key, value) — sets the given key to value . The provided key will be added to the map if it doesn’t exist yet
  • delete(key) — deletes the given key and its value from the map
  • get(key) — returns the value at the given key or undefined if the key isn’t found
  • keys() — returns an iterator to iterate through all the keys in a map in the order that they’re inserted
  • values() — returns an iterator to iterate through all values present in the map in the order that they’re inserted.
  • entries() — returns an iterator to iterate through all key-value pairs in the map with each entry being an array with the key and value.
  • forEach(callback) — runs the callback for each key/value pair in the map
  • clear() — removes all entries from a map
  • size — returns the number of entries in a map
  • toJS() — converts an Observable map to a normal map
  • toJSON() — returns a shallow plain object representation of the map. We can use mobx.toJS(map) to deep copy a map
  • intercept(interceptor) — registers an interceptor that’ll be triggered before any changes are applied to the map
  • observe(listener, fireImmediately?) — attaches a listener that listens to changes in the map
  • merge(values) — copy all entries from the provided object into the map
  • replace(values) — replaces the entire contents of the map with the provided values.

Options

The second argument takes some options to modify the Observable map’s behavior.

We can pass in { deep: false } to create a shallow map to prevent a nested Observable map from being created.

For example, if we write:

import { observable, autorun } from "mobx";
const values = {
  foo: 1,
  bar: {
    baz: 2
  }
};
const observableMap = observable.map(values);
autorun(() => console.log(observableMap.toJS()));

We get a nested Observable map since we have a nested object. The value of bar is an Observable map.

On the other hand, if we write:

import { observable, autorun } from "mobx";
const values = {
  foo: 1,
  bar: {
    baz: 2
  }
};
const observableMap = observable.map(values, { deep: false });
autorun(() => console.log(observableMap.toJS()));

Then we get the plain object:

{
  baz: 2
}

as the value of bar .

Another option is the { name: "my map" } option, which names the Observable map so we can identify it when we’re debugging with MobX dev tools.

Conclusion

We can create maps that are observable with MobX. It’s like a regular JavaScript map except that it has additional methods.

Also, we can watch is value and manipulate it like a regular map.

By default, Observable maps are recursive, so the values of an Observable map entry may also be an Observable map. We can set the deep option to false to disable this behavior and then the nested values will become a plain object.

Categories
Mobx

Create Mobx Observable Objects and Arrays

Mobx is a state management solution for JavaScript apps. It lets us observe values from a store and then set the values, which will be immediately reflected in the store.

In this article, we’ll look at how to create Observable objects with Mobx.

Observable Objects

Mobx provides us with the observable.object method, which can be called as follows:

observable.object(props, decorators?, options?)

If a plain JavaScript object is passed to the observable then all properties inside will be cloned and made observable.

A plain object is an object that wasn’t created from a constructor but has Object as its prototype or no prototype.

observable is applied recursively by default, so if the encountered value is an object or array, the value will also be passed through observable .

We can use the observable.object method as follows:

import { observable, autorun, observe, action } from "mobx";

const person = observable(
  {
    firstName: "Jane",
    lastName: "Smith",
    get fullName() {
      return `${this.firstName}, ${this.lastName}`;
    },
    setAge(age) {
      this.age = age;
    }
  },
  {
    setAge: action
  }
);

observe(person, ({ newValue, oldValue }) => console.log(newValue, oldValue));
person.setAge(20);

We created the Observable person object with the observable function, which takes a plain object as the first argument. Then we set the setAge method in the object as an action with the second object.

We can then observe value changes with the observe method, which takes the Observable person object as the first argument and a change listener as the second argument.

Then when we run:

person.setAge(20);

the listener that we passed into observe will run.

In MobX 4 or lower, when we pass objects into observable, only propeties that exist at the time of making the object observable will be observable. Properties that are added to the object at a later time won’t be observable unless set or extendObservable os used.

Only plain objects will be made observable. For non-plain objects, it’s considered the responsibility of the constructor to initialize the observable properties.

We can either use the @observale annotation or the extendObservable function.

Property getters will be automatically turned into derived properties.

oberservable is applied recursively to the whole object automatically on instantiation and also when new values are assigned to the observable properties. It’s won’t apply observable recursively to non-plain objects.

We can pass in { deep: false } to observable ‘s 3rd argument to disable automatic conversion of property values.

Also, we can use the autorun function to watch the values that are changed as follows:

import { observable, autorun, action } from "mobx";

const person = observable(
  {
    firstName: "Jane",
    lastName: "Smith",
    get fullName() {
      return `${this.firstName}, ${this.lastName}`;
    },
    setAge(age) {
      this.age = age;
    }
  },
  {
    setAge: action
  },
  {
    name: "person"
  }
);

autorun(() => console.log(person.age));
autorun(() => console.log(person.firstName));

person.setAge(20);
person.firstName = "Joe";

In the code above, we’ll see the callbacks we passed into autorun run when we update person.age via setAge or when we update a property directly as we did with firstName .

Observable Arrays

We can use the observable.array method to create an Observable array. It takes an array as the first argument.

For example, we can use it as follows:

import { observable, autorun } from "mobx";

const fruits = observable(["apple", "orange"]);

autorun(() => console.log(fruits.join(", ")));

fruits.push("grape");
fruits[0] = "pear";

In the code above, we passed in array to observable . Then we runautorun with a callback that runs every time fruits change.

Then when we run the array operations on fruits , we get the latest value in the callback we passed into autorun .

The following methods are also available on observable arrays:

  • intercept(interceptor) — intercept array change operations
  • observe(listener, fireImmediately? = false) — listen to array changes
  • clear() — remove all entries from an array
  • replace(newItems) — replace all entries with new ones.
  • find(predicate: (item, index, array) => boolean, thisArg?) — same as the usual Array.prototype.find method
  • findIndex(predicate: (item, index, array) => boolean, thisArg?) — same as the usual Array.prototype.findIndex method
  • remove(value) — remove a single item by value from an array. It returns true is the item is found and removed

We can pass in { deep: false } as the second argument of observable.array to prevent setting items to be observable recursively.

Also, we can pass in { name: "my array" } as the second argument to add a name for debugging purposes.

Conclusion

We can create Observable objects with the observable.object and the observable.array method to create Observable arrays.

We can watch for value changes with autorun which will give us the latest values as the observable object changes.

Also, we can set various options to control how the observable values will be converted.

Categories
Mobx

Creating Observables with Mobx

Mobx is a state management solution for JavaScript apps. It lets us observe values from a store and then set the values, which will be immediately reflected in the store.

In this article, we’ll look at how to create an Observable that lets us subscribe to it and then change the value of it.

The observable Function

The Mobx observable function takes an object. It’s also available in decorator form where we can set the value of it directly.

It can be used as follows:

  • observable(value)
  • @observable classProperty = value

It returns various kinds of values depending on what value is passed in. The value can be JavaScript primitive values, references, plain objects, class instances, arrays, and maps.

There’re some conversion rules applied to values that are passed into the observable function. They’re the following:

  • If value is an ES6 Map , then a new Observable Map will be returned. Observable maps are very useful if we want to react to changes in a specific entry, and addition or removal of entries.
  • If value is an ES6 Set , then a new Observable Set will be returned.
  • If value is an array, then a new Observable array will be returned.
  • If value is an object without a prototype, all its current properties will be made observable
  • If value is an object with a prototype, a JavaScript primitive or function, the observable will throw. We should use Boxed Observable observables instead.

For example, we can use observable as follows:

import { observable } from "mobx";
const map = observable.map({ foo: "value" });
map.observe(({ oldValue, newValue }) => console.log(oldValue, newValue));

map.set("foo", "new value");

In the code above, we have the observable.map function to create an Observable Map.

Then we attach a listener to it to watch the values with the observe function. The listener gives us an object with the oldValue and newValue properties so we can retrieve them.

When we call:

map.set("foo", "new value");

then we’ll see the values logged by the listener.

We can also do something similar with arrays:

import { observable } from "mobx";
const array = observable([1, 2, 3]);
array.observe(({ oldValue, newValue }) => console.log(oldValue, newValue));

array[1] = 5;

We passed in an array to the observable function which returns an Observable array. Then we can call observe on it to watch its values like before.

Then when we make changes to array , the listener then we pass into observe will log the new and old values.

To make primitives observable, we can use the box method as follows:

import { observable } from "mobx";
const num = observable.box(3);
num.observe(({ oldValue, newValue }) => console.log(oldValue, newValue));

num.set(10);

In the code above, we created an observable primitive with the box method. The code for observing the value change is the same as before.

Then we can use set to set a new value for the num Observable primitive.

The @observable Decorator

If our codebase has support for ES7 or TypeScript, then we can use the @observable decorator to make class properties observable.

We can use them as follows:

import { observable, computed } from "mobx";

class Person {
  @observable firstName = "Jane";
  @observable lastName = "Smith";

  @computed get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const person = new Person();
console.log(person.firstName);
console.log(person.lastName);
console.log(person.fullName);

In the code above, we created the Person class which has Observable properties as indicated by using the observable decorator. We also have a computed property fullName which is a getter.

Then we create a new Person instance and we can log the values.

We can also set the values by assigning a value as usual.

To use decorators, we can use a build system like Parcel and then add the @babel/core , @babel/plugin-proposal-class-properties, @babel/plugin-proposal-decorators , and @babel/preset-env packages and all them to .babelrc in the plugins section as follows:

{
  "presets": [
    "@babel/preset-env"
  ],
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ],
    [
      "@babel/plugin-proposal-class-properties",
      {
        "loose": true
      }
    ]
  ]
}

Conclusion

We can create Observable values with the observable object. It can be used with various kinds of objects as long as it’s used with the correct method to create an Observable value.

Then we can use the observe method to observe the value of the returned Observable and manipulate it.

We can also use the observable decorator with classes to create a class with Observable properties.

To use decorators we either use Babel or TypeScript.

Categories
Mobx

Simple State Management with Mobx

Mobx is a simpler alternative to state management for React apps. It works by creating a store and then watching it for changes, and we update the store by changing the values directly.

In this article, we’ll look at how to use Mobx and React together.

Installation

We install the mobx and mobx-react libraries to create our Mobx store and then connect it to our React component.

To do this, we run:

npm install mobx mobx-react

All modern browsers are supported by Mobx with version 4.x or earlier. Since version 5, Mobx uses proxies for state updates so Internet Explorer isn’t supported. It also works with React Native and Node.js

Basic Usage

To start using it, we create a store to hold the state. Then we can pass the store into our component.

For example, we can use Mobx to create a store then inject the store into our React component as follows:

import React from "react";
import ReactDOM from "react-dom";
import { observable } from "mobx";
import { observer } from "mobx-react";

class Count {
  @observable count = 0;
}

const App = observer(({ store }) => {
  return (
    <div className="App">
      <button
        onClick={() => {
          store.count++;
        }}
      >
        Increment
      </button>
      <button
        onClick={() => {
          store.count--;
        }}
      >
        Decrement
      </button>
      <p>{store.count}</p>
    </div>
  );
});

const store = new Count();
const rootElement = document.getElementById("root");
ReactDOM.render(<App store={store} />, rootElement);

In the code above, we created a store by defining the Count class:

class Count {
  @observable count = 0;
}

Then we create a new instance of the Count class so that we can pass it into our component as a prop by writing:

const store = new Count();

and:

ReactDOM.render(<App store={store} />, rootElement);

Then to define our App function component, we wrap a function around the observer function so that the latest values from the store will be observed.

This means that once we pass in the store , we’ll get the latest values automatically, and when we change the store properties that have the @observable decorator before it, the value will be propagated to the store.

Therefore, in our onClick props, we can pass in a function that changes store.count directly to update the values, which then will immediately be reflected in store.count that we added in the p element.

We can also use the observer decorator with React class component as follows:

import React from "react";
import ReactDOM from "react-dom";
import { observable, computed, autorun } from "mobx";
import { observer } from "mobx-react";

class Person {
  @observable firstName = "Jane";
  @observable lastName = "Smith";
  @computed
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

@observer
class App extends React.Component {
  render() {
    const { store } = this.props;
    return (
      <div className="App">
        <p>{store.fullName}</p>
      </div>
    );
  }
}

const store = new Person();
const rootElement = document.getElementById("root");
ReactDOM.render(<App store={store} />, rootElement);

It does the same thing as the observer function with React function components.

Computed Values and Getters

We can create a function in our store class that returns a value that’s computed from other observable values.

To do this, we can use the @computed decorator before our method. We can define one and use it as follows:

import React from "react";
import ReactDOM from "react-dom";
import { observable, computed } from "mobx";
import { observer } from "mobx-react";

class Person {
  @observable firstName = "Jane";
  @observable lastName = "Smith";
  @computed
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const App = observer(({ store }) => {
  return (
    <div className="App">
      <p>{store.fullName}</p>
    </div>
  );
});

const store = new Person();
const rootElement = document.getElementById("root");
ReactDOM.render(<App store={store} />, rootElement);

In the code above, we have a method fullName in the Person class that is a getter and it returns `${this.firstName} ${this.lastName}`, which are the 2 observable properties we defined combined.

Then we can get the fullname property in the store prop as we did in the App component.

Custom Reactions

Custom reactions are created by using the autorun, reaction, or when functions by passing in a callback to them. Reactions are used for committing side effects.

For example, we can create them as follows:

autorun(() => {
  console.log(store.fullName);
});

In the code above, we created a reaction that runs automatically when we run the code.

We log the value of store.fullName from above, and we can use that in our component as follows:

const App = observer(({ store }) => {
  autorun(() => {
    console.log(store.fullName);
  });

  return (
    <div className="App">
      <p>{store.fullName}</p>
    </div>
  );
});

Conclusion

We can use Mobx to create a simple store to store our values. It comes with decorators and functions to get us to watch the values from React components and manipulate the store values directly.

With the observable decorator, we can watch the values from the store and then save change the values directly.

The computed decorator lets us get the functions from getters in the store class in our components.

To commit side effects, we can add custom reactions.