Utility functions and common patterns for MobX
Utility functions and common patterns for MobX This package provides utility functions and common MobX patterns build on top of MobX. It is encouraged to take a peek under the hood and read the sources of these utilities. Feel free to open a PR with your own utilities. For large new features, please open an issue first.
NPM: npm install mobx-utils --save
CDN:
import {function_name} from 'mobx-utils'
fromPromise takes a Promise, extends it with 2 observable properties that track
the status of the promise and returns it. The returned object has the following observable properties:
value: either the initial value, the value the Promise resolved to, or the value the Promise was rejected with. use .state if you need to be able to tell the difference.state: one of "pending", "fulfilled" or "rejected"And the following methods:
case({fulfilled, rejected, pending}): maps over the result using the provided handlers, or returns undefined if a handler isn't available for the current promise state.The returned object implements PromiseLike, so you can chain additional Promise handlers using then. You may also use it with await in async functions.
Note that the status strings are available as constants:
mobxUtils.PENDING, mobxUtils.REJECTED, mobxUtil.FULFILLED
fromPromise takes an optional second argument, a previously created fromPromise based observable.
This is useful to replace one promise based observable with another, without going back to an intermediate
"pending" promise state while fetching data. For example:
origPromise The promise which will be observedoldPromise The previously observed promise……Returns any origPromise with added properties and methods described above.
Returns true if the provided value is a promise-based observable.
value anyReturns boolean
Moves an item from one position to another, checking that the indexes given are within bounds.
const source = observable([1, 2, 3])
moveItem(source, 0, 1)
console.log(source.map(x => x)) // [2, 1, 3]Returns ObservableArray<T>
lazyObservable creates an observable around a fetch method that will not be invoked
until the observable is needed the first time.
The fetch method receives a sink callback which can be used to replace the
current value of the lazyObservable. It is allowed to call sink multiple times
to keep the lazyObservable up to date with some external resource.
Note that it is the current() call itself which is being tracked by MobX,
so make sure that you don't dereference to early.
fetch initialValue T optional initialValue that will be returned from current as long as the sink has not been called at least once (optional, default undefined)const userProfile = lazyObservable(
sink => fetch("/myprofile").then(profile => sink(profile))
)
// use the userProfile in a React component:
const Profile = observer(({ userProfile }) =>
userProfile.current() === undefined
?
:
)
// triggers refresh the userProfile
userProfile.refresh()fromResource creates an observable whose current state can be inspected using .current(),
and which can be kept in sync with some external datasource that can be subscribed to.
The created observable will only subscribe to the datasource if it is in use somewhere,
(un)subscribing when needed. To enable fromResource to do that two callbacks need to be provided,
one to subscribe, and one to unsubscribe. The subscribe callback itself will receive a sink callback, which can be used
to update the current state of the observable, allowing observes to react.
Whatever is passed to sink will be returned by current(). The values passed to the sink will not be converted to
observables automatically, but feel free to do so.
It is the current() call itself which is being tracked,
so make sure that you don't dereference to early.
For inspiration, an example integration with the apollo-client on github,
or the implementation of mobxUtils.now
The following example code creates an observable that connects to a dbUserRecord,
which comes from an imaginary database and notifies when it has changed.
subscriber unsubscriber IDisposer (optional, default NOOP)initialValue T the data that will be returned by get() until the sink has emitted its first data (optional, default undefined)…Converts an expression to an observable stream (a.k.a. TC 39 Observable / RxJS observable). The provided expression is tracked by mobx as long as there are subscribers, automatically emitting when new values become available. The expressions respect (trans)actions.
expression fireImmediately boolean (by default false)const user = observable({
firstName: "C.S",
lastName: "Lewis"
})
Rx.Observable
.from(mobxUtils.toStream(() => user.firstname + user.lastName))
.scan(nameChanges => nameChanges + 1, 0)
.subscribe(nameChanges => console.log("Changed name ", nameChanges, "times"))Returns IObservableStream<T>
createViewModel takes an object with observable properties (model)
and wraps a viewmodel around it. The viewmodel proxies all enumerable properties of the original model with the following behavior:
submit() is called.The viewmodel exposes the following additional methods, besides all the enumerable properties of the model:
model: The original model object for which this viewModel was createdsubmit(): Copies all the values of the viewmodel to the model and resets the statereset(): Resets the state of the viewmodel, abandoning all local modificationsresetProperty(propName): Resets the specified property of the viewmodel, abandoning local modifications of this propertyisDirty: Observable property indicating if the viewModel contains any modificationsisPropertyDirty(propName): Returns true if the specified property is dirtychangedValues: Returns a key / value map with the properties that have been changed in the model so farYou may use observable arrays, maps and objects with createViewModel but keep in mind to assign fresh instances of those to the viewmodel's properties, otherwise you would end up modifying the properties of the original model.
Note that if you read a non-dirty property, viewmodel only proxies the read to the model. You therefore need to assign a fresh instance not only the first time you make the assignment but also after calling reset() or submit().
model T…MobX normally suspends any computed value that is not in use by any reaction,
and lazily re-evaluates the expression if needed outside a reaction while not in use.
keepAlive marks a computed value as always in use, meaning that it will always fresh, but never disposed automatically.
_1 _2 target Object an object that has a computed property, created by @computed or extendObservableproperty string the name of the property to keep aliveconst obj = observable({
number: 3,
doubler: function() { return this.number * 2 }
})
const stop = keepAlive(obj, "doubler")Returns IDisposer stops this keep alive so that the computed value goes back to normal behavior
_1 _2 computedValue IComputedValue<any> created using the computed functionconst number = observable(3)
const doubler = computed(() => number.get() * 2)
const stop = keepAlive(doubler)
// doubler will now stay in sync reactively even when there are no further observers
stop()
// normal behavior, doubler results will be recomputed if not observed but needed, but lazilyReturns IDisposer stops this keep alive so that the computed value goes back to normal behavior
queueProcessor takes an observable array, observes it and calls processor
once for each item added to the observable array, optionally debouncing the action
No open issues yet, or sync has not completed.