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 from intercept(user, "name", interceptor). The first tries to add an interceptor to the current value inside user.name (which might not be an observable at all). The latter intercepts changes to the name property of user.

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 the listener two parameters: newValue, oldValue.
  • invokeImmediately: By default false. Set to true if you want observe to invoke listener 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 from observe(user, "name", listener). The first observes the current value inside user.name (which might not be an observable at all), the latter observes the name property of user.

The function returns a disposer function that can be used to cancel the observer.

Note:

transaction does not affect the working of the observe 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 to observe.

observe reacts to mutations, when they are being made, while reactions like autorun or reaction 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 event
  • type (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 using observe(object, 'computedPropertyName', listener).

results matching ""

    No results matching ""