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 operationsobserve(listener, fireImmediately? = false)
— listen to array changesclear()
— remove all entries from an arrayreplace(newItems)
— replace all entries with new ones.find(predicate: (item, index, array) => boolean, thisArg?)
— same as the usualArray.prototype.find
methodfindIndex(predicate: (item, index, array) => boolean, thisArg?)
— same as the usualArray.prototype.findIndex
methodremove(value)
— remove a single item by value from an array. It returnstrue
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.