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 Component
is a shorthand for the@inject() @observer
combo
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 currentvalue
insideuser.name
(which might not be an observable at all). The latter intercepts changes to thename
property 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
change
object as-is from the function, in which case the mutation will be applied. - Modify the
change
object 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 thelistener
two parameters:newValue
,oldValue
.invokeImmediately
: By defaultfalse
. Set totrue
if you wantobserve
to invokelistener
directly 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 currentvalue
insideuser.name
(which might not be an observable at all), the latter observes thename
property ofuser
.
The function returns a disposer
function that can be used to cancel the observer.
Note:
transaction
does not affect the working of theobserve
method(s). This means that even inside a transaction,observe
will fire its listeners for each mutation. Hence,autorun
is usually a more powerful and declarative alternative toobserve
.
observe
reacts to mutations, when they are being made, while reactions likeautorun
orreaction
react 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
update
events 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)
.