mobx-utils
- Version 6.1.0
- Published
- 292 kB
- No dependencies
- MIT license
Install
npm i mobx-utils
yarn add mobx-utils
pnpm add mobx-utils
Overview
Utility functions and common patterns for MobX
Index
Variables
Functions
- addHiddenProp()
- chunkProcessor()
- computedFn()
- createTransformer()
- createViewModel()
- deepObserve()
- expr()
- fail()
- fromPromise()
- fromResource()
- fromStream()
- getAllMethodsAndProperties()
- IDENTITY()
- invariant()
- isPromiseBasedObservable()
- keepAlive()
- lazyObservable()
- moveItem()
- NOOP()
- now()
- queueProcessor()
- resetNowInternalState()
- toStream()
Classes
Interfaces
Type Aliases
Namespaces
Variables
Functions
function addHiddenProp
addHiddenProp: (object: any, propName: string, value: any) => void;
function chunkProcessor
chunkProcessor: <T>( observableArray: T[], processor: (item: T[]) => void, debounce?: number, maxChunkSize?: number) => IDisposer;
chunkProcessor
takes an observable array, observes it and callsprocessor
once for a chunk of items added to the observable array, optionally deboucing the action. The maximum chunk size can be limited by number. This allows both, splitting larger into smaller chunks or (when debounced) combining smaller chunks and/or single items into reasonable chunks of work.Parameter observableArray
observable array instance to track
Parameter processor
action to call per item
Parameter debounce
optional debounce time in ms. With debounce 0 the processor will run synchronously
Parameter maxChunkSize
optionally do not call on full array but smaller chunks. With 0 it will process the full array.
Returns
{IDisposer} stops the processor
Example 1
const trackedActions = observable([]) const stop = chunkProcessor(trackedActions, chunkOfMax10Items => { sendTrackedActionsToServer(chunkOfMax10Items); }, 100, 10)
// usage: trackedActions.push("scrolled") trackedActions.push("hoveredButton") // when both pushes happen within 100ms, there will be only one call to server
function computedFn
computedFn: <T extends (...args: any[]) => any>( fn: T, keepAliveOrOptions?: IComputedFnOptions<T> | boolean) => T;
computedFn takes a function with an arbitrary amount of arguments, and memoizes the output of the function based on the arguments passed in.
computedFn(fn) returns a function with the very same signature. There is no limit on the amount of arguments that is accepted. However, the amount of arguments must be constant and default arguments are not supported.
By default the output of a function call will only be memoized as long as the output is being observed.
The function passes into
computedFn
should be pure, not be an action and only be relying on observables.Setting
keepAlive
totrue
will cause the output to be forcefully cached forever. Note that this might introduce memory leaks!Parameter fn
Parameter keepAliveOrOptions
Example 1
const store = observable({ a: 1, b: 2, c: 3, m: computedFn(function(x) { return this.a * this.b * x }) })
const d = autorun(() => { // store.m(3) will be cached as long as this autorun is running console.log(store.m(3) * store.c) })
function createTransformer
createTransformer: { <A, B>( transformer: ITransformer<A, B>, onCleanup?: ITransformerCleanup<A, B> ): ITransformer<A, B>; <A, B>( transformer: ITransformer<A, B>, arg2?: ITransformerParams<A, B> ): ITransformer<A, B>;};
function createViewModel
createViewModel: <T>(model: T) => T & IViewModel<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: - as long as no new value has been assigned to the viewmodel property, the original property will be returned. - any future change in the model will be visible in the viewmodel as well unless the viewmodel property was dirty at the time of the attempted change. - once a new value has been assigned to a property of the viewmodel, that value will be returned during a read of that property in the future. However, the original model remain untouched untilsubmit()
is called.The viewmodel exposes the following additional methods, besides all the enumerable properties of the model: -
submit()
: copies all the values of the viewmodel to the model and resets the state -reset()
: resets the state of the viewmodel, abandoning all local modifications -resetProperty(propName)
: resets the specified property of the viewmodel -isDirty
: observable property indicating if the viewModel contains any modifications -isPropertyDirty(propName)
: returns true if the specified property is dirty -changedValues
: returns a key / value map with the properties that have been changed in the model so far -model
: The original model object for which this viewModel was createdYou 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 callingreset()
orsubmit()
.Parameter model
Returns
{(T & IViewModel)} ```
Example 1
class Todo { @observable title = "Test" }
const model = new Todo() const viewModel = createViewModel(model);
autorun(() => console.log(viewModel.model.title, ",", viewModel.title)) // prints "Test, Test" model.title = "Get coffee" // prints "Get coffee, Get coffee", viewModel just proxies to model viewModel.title = "Get tea" // prints "Get coffee, Get tea", viewModel's title is now dirty, and the local value will be printed viewModel.submit() // prints "Get tea, Get tea", changes submitted from the viewModel to the model, viewModel is proxying again viewModel.title = "Get cookie" // prints "Get tea, Get cookie" // viewModel has diverged again viewModel.reset() // prints "Get tea, Get tea", changes of the viewModel have been abandoned
function deepObserve
deepObserve: <T = any>( target: T, listener: (change: IChange, path: string, root: T) => void) => IDisposer;
Given an object, deeply observes the given object. It is like
observe
from mobx, but applied recursively, including all future children.Note that the given object cannot ever contain cycles and should be a tree.
As benefit: path and root will be provided in the callback, so the signature of the listener is (change, path, root) => void
The returned disposer can be invoked to clean up the listener
deepObserve cannot be used on computed values.
Example 1
const disposer = deepObserve(target, (change, path) => { console.dir(change) })
function expr
expr: <T>(expr: () => T) => T;
expr
can be used to create temporary computed values inside computed values. Nesting computed values is useful to create cheap computations in order to prevent expensive computations from needing to run. In the following example the expression prevents that a component is rerender _each time_ the selection changes; instead it will only rerenders when the current todo is (de)selected.expr(func)
is an alias forcomputed(func).get()
. Please note that the function given toexpr
is evaluated _twice_ in the scenario that the overall expression value changes. It is evaluated the first time when any observables it depends on change. It is evaluated a second time when a change in its value triggers the outer computed or reaction to evaluate, which recreates and reevaluates the expression.In the following example, the expression prevents the
TodoView
component from being re-rendered if the selection changes elsewhere. Instead, the component will only re-render when the relevant todo is (de)selected, which happens much less frequently.Example 1
const TodoView = observer(({ todo, editorState }) => { const isSelected = mobxUtils.expr(() => editorState.selection === todo) return <div className={isSelected ? "todo todo-selected" : "todo"}>{todo.title} })
function fail
fail: (message: string) => never;
function fromPromise
fromPromise: typeof fromPromise;
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 returnsundefined
if a handler isn't available for the current promise state.The returned object implements
PromiseLike<TValue>
, so you can chain additionalPromise
handlers usingthen
. You may also use it withawait
inasync
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:Parameter origPromise
The promise which will be observed
Parameter oldPromise
The previously observed promise
Returns
origPromise with added properties and methods described above.
Example 1
@observer class SearchResults extends React.Component { @observable.ref searchResults
componentDidUpdate(nextProps) { if (nextProps.query !== this.props.query) this.searchResults = fromPromise( window.fetch("/search?q=" + nextProps.query), // by passing, we won't render a pending state if we had a successful search query before // rather, we will keep showing the previous search results, until the new promise resolves (or rejects) this.searchResults ) }
render() { return this.searchResults.case({ pending: (staleValue) => { return staleValue || "searching" // <- value might set to previous results while the promise is still pending }, fulfilled: (value) => { return value // the fresh results }, rejected: (error) => { return "Oops: " + error } }) } }
Observable promises can be created immediately in a certain state using
fromPromise.reject(reason)
orfromPromise.resolve(value?)
. The main advantage offromPromise.resolve(value)
overfromPromise(Promise.resolve(value))
is that the first _synchronously_ starts in the desired state.It is possible to directly create a promise using a resolve, reject function:
fromPromise((resolve, reject) => setTimeout(() => resolve(true), 1000))
Example 2
const fetchResult = fromPromise(fetch("http://someurl"))
// combine with when.. when( () => fetchResult.state !== "pending", () => { console.log("Got ", fetchResult.value) } )
// or a mobx-react component.. const myComponent = observer(({ fetchResult }) => { switch(fetchResult.state) { case "pending": return Loading... case "rejected": return Ooops... {fetchResult.value} case "fulfilled": return Gotcha: {fetchResult.value} } })
// or using the case method instead of switch:
const myComponent = observer(({ fetchResult }) => fetchResult.case({ pending: () => Loading..., rejected: error => Ooops.. {error}, fulfilled: value => Gotcha: {value}, }))
// chain additional handler(s) to the resolve/reject:
fetchResult.then( (result) => doSomeTransformation(result), (rejectReason) => console.error('fetchResult was rejected, reason: ' + rejectReason) ).then( (transformedResult) => console.log('transformed fetchResult: ' + transformedResult) )
function fromResource
fromResource: { <T>( subscriber: (sink: (newValue: T) => void) => void, unsubscriber?: IDisposer ): IResource<T | undefined>; <T>( subscriber: (sink: (newValue: T) => void) => void, unsubscriber: IDisposer, initialValue: T ): IResource<T>;};
function fromStream
fromStream: { <T>(observable: IObservableStream<T>): IStreamListener<T | undefined>; <T, I>(observable: IObservableStream<T>, initialValue: I): IStreamListener< T | I >;};
Converts a subscribable, observable stream (TC 39 observable / RxJS stream) into an object which stores the current value (as
current
). The subscription can be cancelled through thedispose
method. Takes an initial value as second optional argumentExample 1
const debouncedClickDelta = MobxUtils.fromStream(Rx.Observable.fromEvent(button, 'click') .throttleTime(1000) .map(event => event.clientX) .scan((count, clientX) => count + clientX, 0) )
autorun(() => { console.log("distance moved", debouncedClickDelta.current) })
function getAllMethodsAndProperties
getAllMethodsAndProperties: (x: any) => any;
function IDENTITY
IDENTITY: (_: any) => any;
function invariant
invariant: (cond: boolean, message?: string) => void;
function isPromiseBasedObservable
isPromiseBasedObservable: (value: any) => value is IPromiseBasedObservable<any>;
Returns true if the provided value is a promise-based observable.
Parameter value
any
Returns
{boolean}
function keepAlive
keepAlive: { (target: Object, property: string): IDisposer; (computedValue: IComputedValue<any>): IDisposer;};
function lazyObservable
lazyObservable: { <T>(fetch: (sink: (newValue: T) => void) => void): ILazyObservable< T | undefined >; <T>( fetch: (sink: (newValue: T) => void) => void, initialValue: T ): ILazyObservable<T>;};
function moveItem
moveItem: <T>( target: IObservableArray<T>, fromIndex: number, toIndex: number) => IObservableArray<T> | undefined;
Moves an item from one position to another, checking that the indexes given are within bounds.
Parameter target
Parameter fromIndex
Parameter toIndex
Returns
{ObservableArray}
Example 1
const source = observable([1, 2, 3]) moveItem(source, 0, 1) console.log(source.map(x => x)) // [2, 1, 3]
function NOOP
NOOP: () => void;
function now
now: (interval?: number | 'frame') => number;
Returns the current date time as epoch number. The date time is read from an observable which is updated automatically after the given interval. So basically it treats time as an observable.
The function takes an interval as parameter, which indicates how often
now()
will return a new value. If no interval is given, it will update each second. If "frame" is specified, it will update each time arequestAnimationFrame
is available.Multiple clocks with the same interval will automatically be synchronized.
Countdown example: https://jsfiddle.net/mweststrate/na0qdmkw/
Parameter interval
interval in milliseconds about how often the interval should update
Returns
Example 1
const start = Date.now()
autorun(() => { console.log("Seconds elapsed: ", (mobxUtils.now() - start) / 1000) })
function queueProcessor
queueProcessor: <T>( observableArray: T[], processor: (item: T) => void, debounce?: number) => IDisposer;
queueProcessor
takes an observable array, observes it and callsprocessor
once for each item added to the observable array, optionally debouncing the actionParameter observableArray
observable array instance to track
Parameter processor
action to call per item
Parameter debounce
optional debounce time in ms. With debounce 0 the processor will run synchronously
Returns
{IDisposer} stops the processor
Example 1
const pendingNotifications = observable([]) const stop = queueProcessor(pendingNotifications, msg => { // show Desktop notification new Notification(msg); })
// usage: pendingNotifications.push("test!")
function resetNowInternalState
resetNowInternalState: () => void;
Disposes of all the internal Observables created by invocations of
now()
.The use case for this is to ensure that unit tests can run independent of each other. You should not call this in regular application code.
Example 1
afterEach(() => { utils.resetNowInternalState() })
function toStream
toStream: <T>( expression: () => T, fireImmediately?: boolean) => IObservableStream<T>;
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.
Parameter expression
Parameter fireImmediately
(by default false)
Returns
{IObservableStream}
Example 1
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"))
T
Classes
class ObservableGroupMap
class ObservableGroupMap<G, T> extends ObservableMap<G, IObservableArray<T>> {}
Reactively sorts a base observable array into multiple observable arrays based on the value of a
groupBy: (item: T) => G
function.This observes the individual computed groupBy values and only updates the source and dest arrays when there is an actual change, so this is far more efficient than, for example
base.filter(i => groupBy(i) === 'we')
. Call #dispose() to stop tracking.No guarantees are made about the order of items in the grouped arrays.
The resulting map of arrays is read-only. clear(), set(), delete() are not supported and modifying the group arrays will lead to undefined behavior.
NB: ObservableGroupMap relies on
Symbol
s. If you are targeting a platform which doesn't support these natively, you will need to provide a polyfill.Parameter base
The array to sort into groups.
Parameter groupBy
The function used for grouping.
Parameter options
Object with properties:
name
: Debug name of this ObservableGroupMap.keyToName
: Function to create the debug names of the observable group arrays.Example 1
const slices = observable([ { day: "mo", hours: 12 }, { day: "tu", hours: 2 }, ]) const slicesByDay = new ObservableGroupMap(slices, (slice) => slice.day) autorun(() => console.log( slicesByDay.get("mo")?.length ?? 0, slicesByDay.get("we"))) // outputs 1, undefined slices[0].day = "we" // outputs 0, [{ day: "we", hours: 12 }]
constructor
constructor( base: IObservableArray<T>, groupBy: (x: T) => G, { name, keyToName }?: { name?: string; keyToName?: (group: G) => string });
method clear
clear: () => void;
method delete
delete: (_key: G) => boolean;
method dispose
dispose: () => void;
Disposes all observers created during construction and removes state added to base array items.
method set
set: (_key: G, _value: IObservableArray<T>) => this;
class ViewModel
class ViewModel<T> implements IViewModel<T> {}
constructor
constructor(model: {});
property changedValues
readonly changedValues: Map<keyof T, T[keyof T]>;
property isDirty
readonly isDirty: boolean;
property isPropertyDirty
isPropertyDirty: (key: keyof T) => boolean;
property localComputedValues
localComputedValues: ObservableMap<keyof T, IComputedValue<T[keyof T]>>;
property localValues
localValues: ObservableMap<keyof T, T[keyof T]>;
property model
model: {};
method reset
reset: () => void;
method resetProperty
resetProperty: (key: keyof T) => void;
method submit
submit: () => void;
Interfaces
interface IBasePromiseBasedObservable
interface IBasePromiseBasedObservable<T> extends PromiseLike<T> {}
property isPromiseBasedObservable
isPromiseBasedObservable: true;
method case
case: <U>(handlers: CaseHandlers<U, T>, defaultFulfilled?: boolean) => U;
interface ILazyObservable
interface ILazyObservable<T> {}
interface IObservableStream
interface IObservableStream<T> {}
method subscribe
subscribe: { (observer?: IStreamObserver<T> | null): ISubscription; (observer?: (value: T) => void): ISubscription;};
interface IResource
interface IResource<T> {}
interface IStreamListener
interface IStreamListener<T> {}
interface IStreamObserver
interface IStreamObserver<T> {}
interface ISubscription
interface ISubscription {}
method unsubscribe
unsubscribe: () => void;
interface IViewModel
interface IViewModel<T> {}
property changedValues
changedValues: Map<keyof T, T[keyof T]>;
property isDirty
isDirty: boolean;
property model
model: T;
method isPropertyDirty
isPropertyDirty: (key: keyof T) => boolean;
method reset
reset: () => void;
method resetProperty
resetProperty: (key: keyof T) => void;
method submit
submit: () => void;
Type Aliases
type IDisposer
type IDisposer = () => void;
type IFulfilledPromise
type IFulfilledPromise<T> = { readonly state: 'fulfilled'; readonly value: T;};
type IPendingPromise
type IPendingPromise<T> = { readonly state: 'pending'; readonly value: T | undefined;};
type IPromiseBasedObservable
type IPromiseBasedObservable<T> = IBasePromiseBasedObservable<T> & (IPendingPromise<T> | IFulfilledPromise<T> | IRejectedPromise);
type IRejectedPromise
type IRejectedPromise = { readonly state: 'rejected'; readonly value: unknown;};
type ITransformer
type ITransformer<A, B> = (object: A) => B;
type ITransformerCleanup
type ITransformerCleanup<A, B> = ( resultObject: B | undefined, sourceObject?: A) => void;
type ITransformerParams
type ITransformerParams<A, B> = { onCleanup?: ITransformerCleanup<A, B>; debugNameGenerator?: (sourceObject?: A) => string; keepAlive?: boolean;} & Omit<IComputedValueOptions<B>, 'name'>;
type PromiseState
type PromiseState = 'pending' | 'fulfilled' | 'rejected';
Namespaces
namespace fromPromise
namespace fromPromise {}
Package Files (17)
- lib/ObservableGroupMap.d.ts
- lib/array.d.ts
- lib/chunk-processor.d.ts
- lib/computedFn.d.ts
- lib/create-transformer.d.ts
- lib/create-view-model.d.ts
- lib/deepObserve.d.ts
- lib/expr.d.ts
- lib/from-promise.d.ts
- lib/from-resource.d.ts
- lib/keep-alive.d.ts
- lib/lazy-observable.d.ts
- lib/mobx-utils.d.ts
- lib/now.d.ts
- lib/observable-stream.d.ts
- lib/queue-processor.d.ts
- lib/utils.d.ts
Dependencies (0)
No dependencies.
Dev Dependencies (17)
Peer Dependencies (1)
Badge
To add a badge like this oneto your package's README, use the codes available below.
You may also use Shields.io to create a custom badge linking to https://www.jsdocs.io/package/mobx-utils
.
- Markdown[![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue)](https://www.jsdocs.io/package/mobx-utils)
- HTML<a href="https://www.jsdocs.io/package/mobx-utils"><img src="https://img.shields.io/badge/jsDocs.io-reference-blue" alt="jsDocs.io"></a>
- Updated .
Package analyzed in 4267 ms. - Missing or incorrect documentation? Open an issue for this package.