Utilities
Here are some utilities that might make working with observable objects or computed values more convenient. More, less trivial utilities can be found in the mobx-utils package.
Provider
(mobx-react package) Can be used to pass stores to child components using React's context mechanism.
inject
(mobx-react package) Higher order component and counterpart of Provider. Can be used to pick stores from React's context and pass it as props to the target component.
inject("store1", "store2")(observer(MyComponent))@inject("store1", "store2") @observer MyComponent@inject((stores, props, context) => props) @observer MyComponent@observer(["store1", "store2"]) My Componentis a shorthand for the@inject() @observercombo
toJS
toJS(observableDataStructure)
Recursively converts an (observable) object to a javascript structure. Supports observable arrays, objects, maps, and primitives. Computed values and other non-enumerable properties won't be part of the result. Cycles are detected and property supported by default, but this can be disabled to improve performance.
For more complex (de)serialization scenarios, one can use serializr.
var obj = mobx.observable({
x: 1
});
var clone = mobx.toJS(obj);
console.log(mobx.isObservableObject(obj)); // true
console.log(mobx.isObservableObject(clone)); // false
useStrict
useStrict(boolean)
Enables / disables strict mode globally. In strict mode, it is not allowed to change any state outside of an action.
isObservable
isObservable(thing, property?)
Returns true if the given thing, or the property of the given thing was made observable by MobX. Optionally accepts a second string parameter to see whether a specific property is observable. Works for all observables, computed values, and disposer functions of reactions.
var person = observable({
firstName: "Sherlock",
lastName: "Holmes"
});
person.age = 3;
isObservable(person); // true
isObservable(person, "firstName"); // true
isObservable(person.firstName); // false (just a string)
isObservable(person, "age"); // false
isObservableObject|Array|Map and isBoxedObservable
isObservableObject(thing)isObservableArray(thing)isObservableMap(thing)isBoxedObservable(thing)
Returns true if... well, do the math.
isArrayLike
isArrayLike(thing)
Returns true if the given thing is either a true JS-array or an observable (MobX-)array. This is intended as convenience/shorthand. Note that observable arrays can be .slice()d to turn them into true JS-arrays.
isAction
isAction(func)
Returns true if the given function is wrapped/decorated with action.
isComputed
isComputed(thing, property?)
Returns true if the given thing is a boxed computed value, or if the designed property is a computed value. Accepts an object and optional propertyName argument.
createTransformer
createTransformer(transformation: A => B, onCleanup?): A = B
Turns a function (that should transform value A into another value B) into a reactive and memoizing function. In other words, if the transformation function computes B given a specific A, the same B will be returned for all other future invocations of the transformation with the same A. However, if A changes, the transformation will be re-applied so that B is updated accordingly. And last, but not least, if nobody is using the transformation on a specific A anymore, its entry will be removed from the memoization table.
With createTransformer it is very easy to transform a complete data graph into another data graph. Transformation functions can be composed so that you can build a tree using lots of small transformations. The resulting data graph will never be stale, it will be kept in sync with the source by applying small patches to the result graph. This makes it very easy to achieve powerful patterns similar to sideways data loading, map-reduce, tracking state history using immutable data structures, etc.
The optional onCleanup function can be used to get a notification when a transformation of an object is no longer needed. This can be used to dispose resources attached to the result of an object, if needed.
Always use transformations inside a reaction like @observer or autorun. Transformations will, like any other computed value, fall back to lazy evaluation if not observed by something, which sort of defeats their purpose.
intercept
intercept(target, propertyName?, interceptor)
API that can be used to intercept changes before they are applied to an observable API. Useful for validation, normalization, or cancellation.
target: The observable to guard.propertyName: Optional parameter to specify a specific property to intercept.interceptor: Callback that will be invoked for each change that is made to the observable. Receives a single change object describing the mutation.
Note:
intercept(user.name, interceptor)is fundamentally different fromintercept(user, "name", interceptor). The first tries to add an interceptor to the currentvalueinsideuser.name(which might not be an observable at all). The latter intercepts changes to thenameproperty ofuser.
The intercept should tell MobX what needs to happen with the current change. Therefore, it should do one of the following things:
- Return the received
changeobject as-is from the function, in which case the mutation will be applied. - Modify the
changeobject and return it, for example to normalize data. Not all fields are modifiable. - Return
null. This indicates that the change can be ignored and shouldn't be applied. This is a powerful concept to make your objects, for example, temporarily immutable. - Throw an exception, for example, if some invariant isn't met.
The function returns a disposer function that can be used to cancel the interceptor when invoked. It is possible to register multiple interceptors to the same observable. They will be chained in registration order. If one of the interceptors returns null or throws an exception, the other interceptors won't be evaluated anymore. It is also possible to register an interceptor both on a parent object and on an individual property. In that case, the parent object interceptors are run before the property interceptors.
const theme = observable({
backgroundColor: "#ffffff"
})
const disposer = intercept(theme, "backgroundColor", change => {
if (!change.newValue) {
// ignore attempts to unset the background color
return null;
}
if (change.newValue.length === 6) {
// correct missing '#' prefix
change.newValue = '#' + change.newValue;
return change;
}
if (change.newValue.length === 7) {
// this must be a properly formatted color code!
return change;
}
if (change.newValue.length > 10) disposer(); // stop intercepting future changes
throw new Error("This doesn't like a color at all: " + change.newValue);
})
observe
observe(target, propertyName?, listener, invokeImmediately?)
Low-level API that can be used to observe a single observable value. Allows you to intercept changes after they have been made.
target: The observable to observe.propertyName: Optional parameter to specify a specific property to observe.listener: Callback that will be invoked for each change that is made to the observable. Receives a single change object describing the mutation, except for boxed observables, which will invoke thelistenertwo parameters:newValue,oldValue.invokeImmediately: By defaultfalse. Set totrueif you wantobserveto invokelistenerdirectly with the state of the observable (instead of waiting for the first change). Not supported (yet) by all kinds of observables.
Note:
observe(user.name, listener)is fundamentally different fromobserve(user, "name", listener). The first observes the currentvalueinsideuser.name(which might not be an observable at all), the latter observes thenameproperty ofuser.
The function returns a disposer function that can be used to cancel the observer.
Note:
transactiondoes not affect the working of theobservemethod(s). This means that even inside a transaction,observewill fire its listeners for each mutation. Hence,autorunis usually a more powerful and declarative alternative toobserve.
observereacts to mutations, when they are being made, while reactions likeautorunorreactionreact to new values when they become available. In many cases, the latter is sufficient.
import {observable, observe} from 'mobx';
const person = observable({
firstName: "Maarten",
lastName: "Luther"
});
const disposer = observe(person, (change) => {
console.log(change.type, change.name, "from", change.oldValue, "to", change.object[change.name]);
});
person.firstName = "Martin";
// Prints: 'update firstName from Maarten to Martin'
disposer();
// Ignore any future updates
// observe a single field
const disposer2 = observe(person, "lastName", (change) => {
console.log("LastName changed to ", change.newValue);
});
Event Overview
The callbacks of intercept and observe will receive an event object, which has at least the following properties:
object: the observable triggering the eventtype(string): the type of the current event
Additional fields that are available per type:
| Observable Type | Event Type | Property | Description | Available during Intercept | Modifiable by Intercept |
|---|---|---|---|---|---|
Object |
add |
name |
name of the property being added | ✓ | |
newValue |
the new value being assigned | ✓ | ✓ | ||
update* |
name |
name of the property being updated | ✓ | ||
newValue |
the new value being assigned | ✓ | ✓ | ||
oldValue |
the value that is replaced | ||||
Array |
splice |
index |
starting index of the splice. Splices are also fired by push, unshift, replace, etc. |
✓ | |
removedCount |
amount of items being removed | ✓ | ✓ | ||
added |
array with items being added | ✓ | ✓ | ||
removed |
array with items that were removed | ||||
addedCount |
amount of items that were added | ||||
update |
index |
index of the single entry that is being updated | ✓ | ||
newValue |
the new value that is / will be assigned | ✓ | ✓ | ||
oldValue |
the old value that was replaced | ||||
Map |
add |
name |
the name of the entry that was added | ✓ | |
newValue |
the new value that is being assigned | ✓ | ✓ | ||
update |
name |
the name of the entry that is being updated | ✓ | ||
newValue |
the new value that is being assigned | ✓ | ✓ | ||
oldValue |
the old value that has been replaced | ||||
delete |
name |
the name of the entry that is being removed | ✓ | ||
oldValue |
the value of the entry that was removed | ||||
Boxed & Computed Observables |
create |
newValue |
the value that was assigned during creation. only available as spy event for boxed observables |
||
update |
newValue |
the new value being assigned | ✓ | ✓ | |
oldValue |
the old value of the observable |
*Note:
Object
updateevents won't fire for updated computated values (as those aren't mutations). But, it is possible to observe them by explicitly subscribing to the specific property usingobserve(object, 'computedPropertyName', listener).