{"id":13452516,"url":"https://github.com/mobxjs/mobx-utils","last_synced_at":"2025-05-12T20:48:29.355Z","repository":{"id":10267111,"uuid":"65153061","full_name":"mobxjs/mobx-utils","owner":"mobxjs","description":"Utility functions and common patterns for MobX","archived":false,"fork":false,"pushed_at":"2025-04-08T09:57:19.000Z","size":1438,"stargazers_count":1203,"open_issues_count":40,"forks_count":128,"subscribers_count":23,"default_branch":"master","last_synced_at":"2025-05-03T17:04:18.368Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mobxjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"open_collective":"mobx"}},"created_at":"2016-08-07T20:37:49.000Z","updated_at":"2025-04-29T21:29:32.000Z","dependencies_parsed_at":"2024-09-30T14:21:26.228Z","dependency_job_id":"51a8e791-de51-463a-97a0-b4d5ccf5b387","html_url":"https://github.com/mobxjs/mobx-utils","commit_stats":{"total_commits":384,"total_committers":84,"mean_commits":4.571428571428571,"dds":0.6145833333333333,"last_synced_commit":"f4d31e8115c208280229843bff0c01924cf8ef97"},"previous_names":[],"tags_count":46,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobxjs%2Fmobx-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobxjs%2Fmobx-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobxjs%2Fmobx-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobxjs%2Fmobx-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mobxjs","download_url":"https://codeload.github.com/mobxjs/mobx-utils/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253819914,"owners_count":21969440,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T07:01:26.343Z","updated_at":"2025-05-12T20:48:29.337Z","avatar_url":"https://github.com/mobxjs.png","language":"TypeScript","funding_links":["https://opencollective.com/mobx"],"categories":["TypeScript","Awesome MobX"],"sub_categories":["Related projects and utilities"],"readme":"# MobX-utils\n\n_Utility functions and common patterns for MobX_\n\n[![Build Status](https://travis-ci.org/mobxjs/mobx-utils.svg?branch=master)](https://travis-ci.org/mobxjs/mobx-utils)\n[![Coverage Status](https://coveralls.io/repos/github/mobxjs/mobx-utils/badge.svg?branch=master)](https://coveralls.io/github/mobxjs/mobx-utils?branch=master)\n[![Join the chat at https://gitter.im/mobxjs/mobx](https://badges.gitter.im/mobxjs/mobx.svg)](https://gitter.im/mobxjs/mobx?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n[![npm](https://img.shields.io/npm/v/mobx-utils)](https://www.npmjs.com/package/mobx-utils)\n\nThis package provides utility functions and common MobX patterns build on top of MobX.\nIt is encouraged to take a peek under the hood and read the sources of these utilities.\nFeel free to open a PR with your own utilities. For large new features, please open an issue first.\n\n# Installation \u0026 Usage\n\nNPM: `npm install mobx-utils --save`\n\nCDN: \u003chttps://unpkg.com/mobx-utils/mobx-utils.umd.js\u003e\n\n`import {function_name} from 'mobx-utils'`\n\n# API\n\n\u003c!-- Generated by documentation.js. Update this documentation by updating the source code. --\u003e\n\n### Table of Contents\n\n-   [fromPromise](#frompromise)\n    -   [Parameters](#parameters)\n    -   [Examples](#examples)\n-   [isPromiseBasedObservable](#ispromisebasedobservable)\n    -   [Parameters](#parameters-1)\n-   [moveItem](#moveitem)\n    -   [Parameters](#parameters-2)\n    -   [Examples](#examples-1)\n-   [lazyObservable](#lazyobservable)\n    -   [Parameters](#parameters-3)\n    -   [Examples](#examples-2)\n-   [fromResource](#fromresource)\n    -   [Parameters](#parameters-4)\n    -   [Examples](#examples-3)\n-   [toStream](#tostream)\n    -   [Parameters](#parameters-5)\n    -   [Examples](#examples-4)\n-   [StreamListener](#streamlistener)\n-   [ViewModel](#viewmodel)\n-   [createViewModel](#createviewmodel)\n    -   [Parameters](#parameters-6)\n    -   [Examples](#examples-5)\n-   [keepAlive](#keepalive)\n    -   [Parameters](#parameters-7)\n    -   [Examples](#examples-6)\n-   [keepAlive](#keepalive-1)\n    -   [Parameters](#parameters-8)\n    -   [Examples](#examples-7)\n-   [queueProcessor](#queueprocessor)\n    -   [Parameters](#parameters-9)\n    -   [Examples](#examples-8)\n-   [chunkProcessor](#chunkprocessor)\n    -   [Parameters](#parameters-10)\n    -   [Examples](#examples-9)\n-   [resetNowInternalState](#resetnowinternalstate)\n    -   [Examples](#examples-10)\n-   [now](#now)\n    -   [Parameters](#parameters-11)\n    -   [Examples](#examples-11)\n-   [expr](#expr)\n    -   [Parameters](#parameters-12)\n    -   [Examples](#examples-12)\n-   [createTransformer](#createtransformer)\n    -   [Parameters](#parameters-13)\n-   [deepObserve](#deepobserve)\n    -   [Parameters](#parameters-14)\n    -   [Examples](#examples-13)\n-   [ObservableGroupMap](#observablegroupmap)\n    -   [Parameters](#parameters-15)\n    -   [Examples](#examples-14)\n-   [ObservableMap](#observablemap)\n-   [defineProperty](#defineproperty)\n-   [defineProperty](#defineproperty-1)\n-   [defineProperty](#defineproperty-2)\n-   [defineProperty](#defineproperty-3)\n-   [defineProperty](#defineproperty-4)\n-   [computedFn](#computedfn)\n    -   [Parameters](#parameters-16)\n    -   [Examples](#examples-15)\n-   [DeepMapEntry](#deepmapentry)\n-   [DeepMap](#deepmap)\n\n## fromPromise\n\n`fromPromise` takes a Promise, extends it with 2 observable properties that track\nthe status of the promise and returns it. The returned object has the following observable properties:\n\n-   `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.\n-   `state`: one of `\"pending\"`, `\"fulfilled\"` or `\"rejected\"`\n\nAnd the following methods:\n\n-   `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.\n\nThe returned object implements `PromiseLike\u003cTValue\u003e`, so you can chain additional `Promise` handlers using `then`. You may also use it with `await` in `async` functions.\n\nNote that the status strings are available as constants:\n`mobxUtils.PENDING`, `mobxUtils.REJECTED`, `mobxUtil.FULFILLED`\n\nfromPromise takes an optional second argument, a previously created `fromPromise` based observable.\nThis is useful to replace one promise based observable with another, without going back to an intermediate\n\"pending\" promise state while fetching data. For example:\n\n### Parameters\n\n-   `origPromise`  The promise which will be observed\n-   `oldPromise`  The previously observed promise\n\n### Examples\n\n```javascript\n@observer\nclass SearchResults extends React.Component {\n  @observable.ref searchResults\n\n  componentDidUpdate(nextProps) {\n    if (nextProps.query !== this.props.query)\n      this.searchResults = fromPromise(\n        window.fetch(\"/search?q=\" + nextProps.query),\n        // by passing, we won't render a pending state if we had a successful search query before\n        // rather, we will keep showing the previous search results, until the new promise resolves (or rejects)\n        this.searchResults\n      )\n  }\n\n  render() {\n    return this.searchResults.case({\n       pending: (staleValue) =\u003e {\n         return staleValue || \"searching\" // \u003c- value might set to previous results while the promise is still pending\n       },\n       fulfilled: (value) =\u003e {\n         return value // the fresh results\n       },\n       rejected: (error) =\u003e {\n         return \"Oops: \" + error\n       }\n    })\n  }\n}\n\nObservable promises can be created immediately in a certain state using\n`fromPromise.reject(reason)` or `fromPromise.resolve(value?)`.\nThe main advantage of `fromPromise.resolve(value)` over `fromPromise(Promise.resolve(value))` is that the first _synchronously_ starts in the desired state.\n\nIt is possible to directly create a promise using a resolve, reject function:\n`fromPromise((resolve, reject) =\u003e setTimeout(() =\u003e resolve(true), 1000))`\n```\n\n```javascript\nconst fetchResult = fromPromise(fetch(\"http://someurl\"))\n\n// combine with when..\nwhen(\n  () =\u003e fetchResult.state !== \"pending\",\n  () =\u003e {\n    console.log(\"Got \", fetchResult.value)\n  }\n)\n\n// or a mobx-react component..\nconst myComponent = observer(({ fetchResult }) =\u003e {\n  switch(fetchResult.state) {\n     case \"pending\": return \u003cdiv\u003eLoading...\u003c/div\u003e\n     case \"rejected\": return \u003cdiv\u003eOoops... {fetchResult.value}\u003c/div\u003e\n     case \"fulfilled\": return \u003cdiv\u003eGotcha: {fetchResult.value}\u003c/div\u003e\n  }\n})\n\n// or using the case method instead of switch:\n\nconst myComponent = observer(({ fetchResult }) =\u003e\n  fetchResult.case({\n    pending:   () =\u003e \u003cdiv\u003eLoading...\u003c/div\u003e,\n    rejected:  error =\u003e \u003cdiv\u003eOoops.. {error}\u003c/div\u003e,\n    fulfilled: value =\u003e \u003cdiv\u003eGotcha: {value}\u003c/div\u003e,\n  }))\n\n// chain additional handler(s) to the resolve/reject:\n\nfetchResult.then(\n  (result) =\u003e  doSomeTransformation(result),\n  (rejectReason) =\u003e console.error('fetchResult was rejected, reason: ' + rejectReason)\n).then(\n  (transformedResult) =\u003e console.log('transformed fetchResult: ' + transformedResult)\n)\n```\n\nReturns **any** origPromise with added properties and methods described above.\n\n## isPromiseBasedObservable\n\nReturns true if the provided value is a promise-based observable.\n\n### Parameters\n\n-   `value`  any\n\nReturns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** \n\n## moveItem\n\nMoves an item from one position to another, checking that the indexes given are within bounds.\n\n### Parameters\n\n-   `target` **ObservableArray\u0026lt;T\u003e** \n-   `fromIndex` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** \n-   `toIndex` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** \n\n### Examples\n\n```javascript\nconst source = observable([1, 2, 3])\nmoveItem(source, 0, 1)\nconsole.log(source.map(x =\u003e x)) // [2, 1, 3]\n```\n\nReturns **ObservableArray\u0026lt;T\u003e** \n\n## lazyObservable\n\n`lazyObservable` creates an observable around a `fetch` method that will not be invoked\nuntil the observable is needed the first time.\nThe fetch method receives a `sink` callback which can be used to replace the\ncurrent value of the lazyObservable. It is allowed to call `sink` multiple times\nto keep the lazyObservable up to date with some external resource.\n\nNote that it is the `current()` call itself which is being tracked by MobX,\nso make sure that you don't dereference to early.\n\n### Parameters\n\n-   `fetch`  \n-   `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`)\n\n### Examples\n\n```javascript\nconst userProfile = lazyObservable(\n  sink =\u003e fetch(\"/myprofile\").then(profile =\u003e sink(profile))\n)\n\n// use the userProfile in a React component:\nconst Profile = observer(({ userProfile }) =\u003e\n  userProfile.current() === undefined\n  ? \u003cdiv\u003eLoading user profile...\u003c/div\u003e\n  : \u003cdiv\u003e{userProfile.current().displayName}\u003c/div\u003e\n)\n\n// triggers refresh the userProfile\nuserProfile.refresh()\n```\n\n## fromResource\n\n`fromResource` creates an observable whose current state can be inspected using `.current()`,\nand which can be kept in sync with some external datasource that can be subscribed to.\n\nThe created observable will only subscribe to the datasource if it is in use somewhere,\n(un)subscribing when needed. To enable `fromResource` to do that two callbacks need to be provided,\none to subscribe, and one to unsubscribe. The subscribe callback itself will receive a `sink` callback, which can be used\nto update the current state of the observable, allowing observes to react.\n\nWhatever is passed to `sink` will be returned by `current()`. The values passed to the sink will not be converted to\nobservables automatically, but feel free to do so.\nIt is the `current()` call itself which is being tracked,\nso make sure that you don't dereference to early.\n\nFor inspiration, an example integration with the apollo-client on [github](https://github.com/apollostack/apollo-client/issues/503#issuecomment-241101379),\nor the [implementation](https://github.com/mobxjs/mobx-utils/blob/1d17cf7f7f5200937f68cc0b5e7ec7f3f71dccba/src/now.ts#L43-L57) of `mobxUtils.now`\n\nThe following example code creates an observable that connects to a `dbUserRecord`,\nwhich comes from an imaginary database and notifies when it has changed.\n\n### Parameters\n\n-   `subscriber`  \n-   `unsubscriber` **IDisposer**  (optional, default `NOOP`)\n-   `initialValue` **T** the data that will be returned by `get()` until the `sink` has emitted its first data (optional, default `undefined`)\n\n### Examples\n\n```javascript\nfunction createObservableUser(dbUserRecord) {\n  let currentSubscription;\n  return fromResource(\n    (sink) =\u003e {\n      // sink the current state\n      sink(dbUserRecord.fields)\n      // subscribe to the record, invoke the sink callback whenever new data arrives\n      currentSubscription = dbUserRecord.onUpdated(() =\u003e {\n        sink(dbUserRecord.fields)\n      })\n    },\n    () =\u003e {\n      // the user observable is not in use at the moment, unsubscribe (for now)\n      dbUserRecord.unsubscribe(currentSubscription)\n    }\n  )\n}\n\n// usage:\nconst myUserObservable = createObservableUser(myDatabaseConnector.query(\"name = 'Michel'\"))\n\n// use the observable in autorun\nautorun(() =\u003e {\n  // printed everytime the database updates its records\n  console.log(myUserObservable.current().displayName)\n})\n\n// ... or a component\nconst userComponent = observer(({ user }) =\u003e\n  \u003cdiv\u003e{user.current().displayName}\u003c/div\u003e\n)\n```\n\n## toStream\n\nConverts an expression to an observable stream (a.k.a. TC 39 Observable / RxJS observable).\nThe provided expression is tracked by mobx as long as there are subscribers, automatically\nemitting when new values become available. The expressions respect (trans)actions.\n\n### Parameters\n\n-   `expression`  \n-   `fireImmediately` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (by default false)\n\n### Examples\n\n```javascript\nconst user = observable({\n  firstName: \"C.S\",\n  lastName: \"Lewis\"\n})\n\nRx.Observable\n  .from(mobxUtils.toStream(() =\u003e user.firstname + user.lastName))\n  .scan(nameChanges =\u003e nameChanges + 1, 0)\n  .subscribe(nameChanges =\u003e console.log(\"Changed name \", nameChanges, \"times\"))\n```\n\nReturns **IObservableStream\u0026lt;T\u003e** \n\n## StreamListener\n\n## ViewModel\n\n## createViewModel\n\n`createViewModel` takes an object with observable properties (model)\nand wraps a viewmodel around it. The viewmodel proxies all enumerable properties of the original model with the following behavior:\n\n-   as long as no new value has been assigned to the viewmodel property, the original property will be returned.\n-   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.\n-   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 until `submit()` is called.\n\nThe viewmodel exposes the following additional methods, besides all the enumerable properties of the model:\n\n-   `submit()`: copies all the values of the viewmodel to the model and resets the state\n-   `reset()`: resets the state of the viewmodel, abandoning all local modifications\n-   `resetProperty(propName)`: resets the specified property of the viewmodel\n-   `isDirty`: observable property indicating if the viewModel contains any modifications\n-   `isPropertyDirty(propName)`: returns true if the specified property is dirty\n-   `changedValues`: returns a key / value map with the properties that have been changed in the model so far\n-   `model`: The original model object for which this viewModel was created\n\nYou 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.\nNote 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()`.\n\n### Parameters\n\n-   `model` **T** \n\n### Examples\n\n```javascript\nclass Todo {\n  @observable title = \"Test\"\n}\n\nconst model = new Todo()\nconst viewModel = createViewModel(model);\n\nautorun(() =\u003e console.log(viewModel.model.title, \",\", viewModel.title))\n// prints \"Test, Test\"\nmodel.title = \"Get coffee\"\n// prints \"Get coffee, Get coffee\", viewModel just proxies to model\nviewModel.title = \"Get tea\"\n// prints \"Get coffee, Get tea\", viewModel's title is now dirty, and the local value will be printed\nviewModel.submit()\n// prints \"Get tea, Get tea\", changes submitted from the viewModel to the model, viewModel is proxying again\nviewModel.title = \"Get cookie\"\n// prints \"Get tea, Get cookie\" // viewModel has diverged again\nviewModel.reset()\n// prints \"Get tea, Get tea\", changes of the viewModel have been abandoned\n```\n\n## keepAlive\n\nMobX normally suspends any computed value that is not in use by any reaction,\nand lazily re-evaluates the expression if needed outside a reaction while not in use.\n`keepAlive` marks a computed value as always in use, meaning that it will always fresh, but never disposed automatically.\n\n### Parameters\n\n-   `_1`  \n-   `_2`  \n-   `target` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** an object that has a computed property, created by `@computed` or `extendObservable`\n-   `property` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the name of the property to keep alive\n\n### Examples\n\n```javascript\nconst obj = observable({\n  number: 3,\n  doubler: function() { return this.number * 2 }\n})\nconst stop = keepAlive(obj, \"doubler\")\n```\n\nReturns **IDisposer** stops this keep alive so that the computed value goes back to normal behavior\n\n## keepAlive\n\n### Parameters\n\n-   `_1`  \n-   `_2`  \n-   `computedValue` **IComputedValue\u0026lt;any\u003e** created using the `computed` function\n\n### Examples\n\n```javascript\nconst number = observable(3)\nconst doubler = computed(() =\u003e number.get() * 2)\nconst stop = keepAlive(doubler)\n// doubler will now stay in sync reactively even when there are no further observers\nstop()\n// normal behavior, doubler results will be recomputed if not observed but needed, but lazily\n```\n\nReturns **IDisposer** stops this keep alive so that the computed value goes back to normal behavior\n\n## queueProcessor\n\n`queueProcessor` takes an observable array, observes it and calls `processor`\nonce for each item added to the observable array, optionally debouncing the action\n\n### Parameters\n\n-   `observableArray` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)\u0026lt;T\u003e** observable array instance to track\n-   `processor`  \n-   `debounce` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** optional debounce time in ms. With debounce 0 the processor will run synchronously (optional, default `0`)\n\n### Examples\n\n```javascript\nconst pendingNotifications = observable([])\nconst stop = queueProcessor(pendingNotifications, msg =\u003e {\n  // show Desktop notification\n  new Notification(msg);\n})\n\n// usage:\npendingNotifications.push(\"test!\")\n```\n\nReturns **IDisposer** stops the processor\n\n## chunkProcessor\n\n`chunkProcessor` takes an observable array, observes it and calls `processor`\nonce for a chunk of items added to the observable array, optionally deboucing the action.\nThe maximum chunk size can be limited by number.\nThis allows both, splitting larger into smaller chunks or (when debounced) combining smaller\nchunks and/or single items into reasonable chunks of work.\n\n### Parameters\n\n-   `observableArray` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)\u0026lt;T\u003e** observable array instance to track\n-   `processor`  \n-   `debounce` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** optional debounce time in ms. With debounce 0 the processor will run synchronously (optional, default `0`)\n-   `maxChunkSize` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** optionally do not call on full array but smaller chunks. With 0 it will process the full array. (optional, default `0`)\n\n### Examples\n\n```javascript\nconst trackedActions = observable([])\nconst stop = chunkProcessor(trackedActions, chunkOfMax10Items =\u003e {\n  sendTrackedActionsToServer(chunkOfMax10Items);\n}, 100, 10)\n\n// usage:\ntrackedActions.push(\"scrolled\")\ntrackedActions.push(\"hoveredButton\")\n// when both pushes happen within 100ms, there will be only one call to server\n```\n\nReturns **IDisposer** stops the processor\n\n## resetNowInternalState\n\nDisposes of all the internal Observables created by invocations of `now()`.\n\nThe use case for this is to ensure that unit tests can run independent of each other.\nYou should not call this in regular application code.\n\n### Examples\n\n```javascript\nafterEach(() =\u003e {\n    utils.resetNowInternalState()\n})\n```\n\n## now\n\nReturns the current date time as epoch number.\nThe date time is read from an observable which is updated automatically after the given interval.\nSo basically it treats time as an observable.\n\nThe function takes an interval as parameter, which indicates how often `now()` will return a new value.\nIf no interval is given, it will update each second. If \"frame\" is specified, it will update each time a\n`requestAnimationFrame` is available.\n\nMultiple clocks with the same interval will automatically be synchronized.\n\nCountdown example: \u003chttps://jsfiddle.net/mweststrate/na0qdmkw/\u003e\n\n### Parameters\n\n-   `interval` **([number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number) \\| `\"frame\"`)** interval in milliseconds about how often the interval should update (optional, default `1000`)\n\n### Examples\n\n```javascript\nconst start = Date.now()\n\nautorun(() =\u003e {\n  console.log(\"Seconds elapsed: \", (mobxUtils.now() - start) / 1000)\n})\n```\n\n## expr\n\n`expr` can be used to create temporary computed values inside computed values.\nNesting computed values is useful to create cheap computations in order to prevent expensive computations from needing to run.\nIn the following example the expression prevents that a component is rerender _each time_ the selection changes;\ninstead it will only rerenders when the current todo is (de)selected.\n\n`expr(func)` is an alias for `computed(func).get()`.\nPlease note that the function given to `expr` is evaluated _twice_ in the scenario that the overall expression value changes.\nIt is evaluated the first time when any observables it depends on change.\nIt 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.\n\nIn the following example, the expression prevents the `TodoView` component from being re-rendered if the selection changes elsewhere.\nInstead, the component will only re-render when the relevant todo is (de)selected, which happens much less frequently.\n\n### Parameters\n\n-   `expr`  \n\n### Examples\n\n```javascript\nconst TodoView = observer(({ todo, editorState }) =\u003e {\n    const isSelected = mobxUtils.expr(() =\u003e editorState.selection === todo)\n    return \u003cdiv className={isSelected ? \"todo todo-selected\" : \"todo\"}\u003e{todo.title}\u003c/div\u003e\n})\n```\n\n## createTransformer\n\nCreates a function that maps an object to a view.\nThe mapping is memoized.\n\nSee the [transformer](#createtransformer-in-detail) section for more details.\n\n### Parameters\n\n-   `transformer`  A function which transforms instances of A into instances of B\n-   `arg2`  An optional cleanup function which is called when the transformation is no longer\n    observed from a reactive context, or config options\n\nReturns **any** The memoized transformer function\n\n## deepObserve\n\nGiven an object, deeply observes the given object.\nIt is like `observe` from mobx, but applied recursively, including all future children.\n\nNote that the given object cannot ever contain cycles and should be a tree.\n\nAs benefit: path and root will be provided in the callback, so the signature of the listener is\n(change, path, root) =\u003e void\n\nThe returned disposer can be invoked to clean up the listener\n\ndeepObserve cannot be used on computed values.\n\n### Parameters\n\n-   `target`  \n-   `listener`  \n\n### Examples\n\n```javascript\nconst disposer = deepObserve(target, (change, path) =\u003e {\n   console.dir(change)\n})\n```\n\n## ObservableGroupMap\n\nReactively sorts a base observable array into multiple observable arrays based on the value of a\n`groupBy: (item: T) =\u003e G` function.\n\nThis observes the individual computed groupBy values and only updates the source and dest arrays\nwhen there is an actual change, so this is far more efficient than, for example\n`base.filter(i =\u003e groupBy(i) === 'we')`. Call #dispose() to stop tracking.\n\nNo guarantees are made about the order of items in the grouped arrays.\n\nThe resulting map of arrays is read-only. clear(), set(), delete() are not supported and\nmodifying the group arrays will lead to undefined behavior.\n\nNB: ObservableGroupMap relies on `Symbol`s. If you are targeting a platform which doesn't\nsupport these natively, you will need to provide a polyfill.\n\n### Parameters\n\n-   `base` **[array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** The array to sort into groups.\n-   `groupBy` **[function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** The function used for grouping.\n-   `options`  Object with properties:\n     `name`: Debug name of this ObservableGroupMap.\n     `keyToName`: Function to create the debug names of the observable group arrays.\n\n### Examples\n\n```javascript\nconst slices = observable([\n    { day: \"mo\", hours: 12 },\n    { day: \"tu\", hours: 2 },\n])\nconst slicesByDay = new ObservableGroupMap(slices, (slice) =\u003e slice.day)\nautorun(() =\u003e console.log(\n    slicesByDay.get(\"mo\")?.length ?? 0,\n    slicesByDay.get(\"we\"))) // outputs 1, undefined\nslices[0].day = \"we\" // outputs 0, [{ day: \"we\", hours: 12 }]\n```\n\n## ObservableMap\n\n## defineProperty\n\nBase observable array which is being sorted into groups.\n\n## defineProperty\n\nThe ObservableGroupMap needs to track some state per-item. This is the name/symbol of the\nproperty used to attach the state.\n\n## defineProperty\n\nThe function used to group the items.\n\n## defineProperty\n\nThis function is used to generate the mobx debug names of the observable group arrays.\n\n## defineProperty\n\nDisposes all observers created during construction and removes state added to base array\nitems.\n\n## computedFn\n\ncomputedFn takes a function with an arbitrary amount of arguments,\nand memoizes the output of the function based on the arguments passed in.\n\ncomputedFn(fn) returns a function with the very same signature. There is no limit on the amount of arguments\nthat is accepted. However, the amount of arguments must be constant and default arguments are not supported.\n\nBy default the output of a function call will only be memoized as long as the\noutput is being observed.\n\nThe function passes into `computedFn` should be pure, not be an action and only be relying on\nobservables.\n\nSetting `keepAlive` to `true` will cause the output to be forcefully cached forever.\nNote that this might introduce memory leaks!\n\n### Parameters\n\n-   `fn`  \n-   `keepAliveOrOptions`  \n\n### Examples\n\n```javascript\nconst store = observable({\na: 1,\nb: 2,\nc: 3,\nm: computedFn(function(x) {\nreturn this.a * this.b * x\n})\n})\n\nconst d = autorun(() =\u003e {\n// store.m(3) will be cached as long as this autorun is running\nconsole.log(store.m(3) * store.c)\n})\n```\n\n## DeepMapEntry\n\n## DeepMap\n\n# Details\n\n## createTransformer in detail\n\nWith `createTransformer` it is very easy to transform a complete data graph into another data graph.\nTransformation functions can be composed so that you can build a tree using lots of small transformations.\nThe resulting data graph will never be stale, it will be kept in sync with the source by applying small patches to the result graph.\nThis makes it very easy to achieve powerful patterns similar to sideways data loading, map-reduce, tracking state history using immutable data structures etc.\n\n`createTransformer` turns a function (that should transform value `A` into another value `B`) into a reactive and memoizing function.\nIn 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`.\nHowever, if `A` changes, or any derivation accessed in the transformer function body gets invalidated, the transformation will be re-applied so that `B` is updated accordingly.\nAnd last but not least, if nobody is using the transformation of a specific A anymore, its entry will be removed from the memoization table.\n\nThe optional `onCleanup` function can be used to get a notification when a transformation of an object is no longer needed.\nThis can be used to dispose resources attached to the result object if needed.\n\nAlways use transformations inside a reaction like `observer` or `autorun`.\n\nTransformations will, like any other computed value, fall back to lazy evaluation if not observed by something, which sort of defeats their purpose.\n\n### Parameters\n\n-   \\`transformation: (value: A) =\u003e B\n-   `onCleanup?: (result: B, value?: A) =\u003e void)`\n-   \n\n`createTransformer\u003cA, B\u003e(transformation: (value: A) =\u003e B, onCleanup?: (result: B, value?: A) =\u003e void): (value: A) =\u003e B`\n\n## Examples\n\nThis all might still be a bit vague, so here are two examples that explain this whole idea of transforming one data structure into another by using small, reactive functions:\n\n### Tracking mutable state using immutable, shared data structures.\n\nThis example is taken from the [Reactive2015 conference demo](https://github.com/mobxjs/mobx-reactive2015-demo):\n\n```javascript\n/*\n    The store that holds our domain: boxes and arrows\n*/\nconst store = observable({\n    boxes: [],\n    arrows: [],\n    selection: null,\n})\n\n/**\n    Serialize store to json upon each change and push it onto the states list\n*/\nconst states = []\n\nautorun(() =\u003e {\n    states.push(serializeState(store))\n})\n\nconst serializeState = createTransformer((store) =\u003e ({\n    boxes: store.boxes.map(serializeBox),\n    arrows: store.arrows.map(serializeArrow),\n    selection: store.selection ? store.selection.id : null,\n}))\n\nconst serializeBox = createTransformer((box) =\u003e ({ ...box }))\n\nconst serializeArrow = createTransformer((arrow) =\u003e ({\n    id: arrow.id,\n    to: arrow.to.id,\n    from: arrow.from.id,\n}))\n```\n\nIn this example the state is serialized by composing three different transformation functions.\nThe autorunner triggers the serialization of the `store` object, which in turn serializes all boxes and arrows.\nLet's take closer look at the life of an imaginary example box#3.\n\n1.  The first time box#3 is passed by `map` to `serializeBox`,\n    the serializeBox transformation is executed and an entry containing box#3 and its serialized representation is added to the internal memoization table of `serializeBox`.\n2.  Imagine that another box is added to the `store.boxes` list.\n    This would cause the `serializeState` function to re-compute, resulting in a complete remapping of all the boxes.\n    However, all the invocations of `serializeBox` will now return their old values from the memoization tables since their transformation functions didn't (need to) run again.\n3.  Secondly, if somebody changes a property of box#3 this will cause the application of the `serializeBox` to box#3 to re-compute, just like any other reactive function in MobX.\n    Since the transformation will now produce a new Json object based on box#3, all observers of that specific transformation will be forced to run again as well.\n    That's the `serializeState` transformation in this case.\n    `serializeState` will now produce a new value in turn and map all the boxes again. But except for box#3, all other boxes will be returned from the memoization table.\n4.  Finally, if box#3 is removed from `store.boxes`, `serializeState` will compute again.\n    But since it will no longer be using the application of `serializeBox` to box#3,\n    that reactive function will go back to non-reactive mode.\n    This signals the memoization table that the entry can be removed so that it is ready for GC.\n\nSo effectively we have achieved state tracking using immutable, shared datas structures here.\nAll boxes and arrows are mapped and reduced into single state tree.\nEach change will result in a new entry in the `states` array, but the different entries will share almost all of their box and arrow representations.\n\n### Transforming a datagraph into another reactive data graph\n\nInstead of returning plain values from a transformation function, it is also possible to return observable objects.\nThis can be used to transform an observable data graph into a another observable data graph, which can be used to transform... you get the idea.\n\nHere is a small example that encodes a reactive file explorer that will update its representation upon each change.\nData graphs that are built this way will in general react a lot faster and will consist of much more straight-forward code,\ncompared to derived data graph that are updated using your own code. See the [performance tests](https://github.com/mobxjs/mobx/blob/3ea1f4af20a51a1cb30be3e4a55ec8f964a8c495/test/perf/transform-perf.js#L4) for some examples.\n\nUnlike the previous example, the `transformFolder` will only run once as long as a folder remains visible;\nthe `DisplayFolder` objects track the associated `Folder` objects themselves.\n\nIn the following example all mutations to the `state` graph will be processed automatically.\nSome examples:\n\n1.  Changing the name of a folder will update its own `path` property and the `path` property of all its descendants.\n2.  Collapsing a folder will remove all descendant `DisplayFolders` from the tree.\n3.  Expanding a folder will restore them again.\n4.  Setting a search filter will remove all nodes that do not match the filter, unless they have a descendant that matches the filter.\n5.  Etc.\n\n```javascript\nimport {extendObservable, observable, createTransformer, autorun} from \"mobx\"\n\nfunction Folder(parent, name) {\n\tthis.parent = parent;\n\textendObservable(this, {\n\t\tname: name,\n\t\tchildren: observable.shallow([]),\n\t});\n}\n\nfunction DisplayFolder(folder, state) {\n\tthis.state = state;\n\tthis.folder = folder;\n\textendObservable(this, {\n\t\tcollapsed: false,\n\t\tget name() {\n\t\t\treturn this.folder.name;\n\t\t},\n\t\tget isVisible() {\n\t\t\treturn !this.state.filter || this.name.indexOf(this.state.filter) !== -1 || this.children.some(child =\u003e child.isVisible);\n\t\t},\n\t\tget children() {\n\t\t\tif (this.collapsed)\n\t\t\t\treturn [];\n\t\t\treturn this.folder.children.map(transformFolder).filter(function(child) {\n\t\t\t\treturn child.isVisible;\n\t\t\t})\n\t\t},\n\t\tget path() {\n\t\t\treturn this.folder.parent === null ? this.name : transformFolder(this.folder.parent).path + \"/\" + this.name;\n\t\t})\n\t});\n}\n\nvar state = observable({\n\troot: new Folder(null, \"root\"),\n\tfilter: null,\n\tdisplayRoot: null\n});\n\nvar transformFolder = createTransformer(function (folder) {\n\treturn new DisplayFolder(folder, state);\n});\n\n\n// returns list of strings per folder\nvar stringTransformer = createTransformer(function (displayFolder) {\n\tvar path = displayFolder.path;\n\treturn path + \"\\n\" +\n\t\tdisplayFolder.children.filter(function(child) {\n\t\t\treturn child.isVisible;\n\t\t}).map(stringTransformer).join('');\n});\n\nfunction createFolders(parent, recursion) {\n\tif (recursion === 0)\n\t\treturn;\n\tfor (var i = 0; i \u003c 3; i++) {\n\t\tvar folder = new Folder(parent, i + '');\n\t\tparent.children.push(folder);\n\t\tcreateFolders(folder, recursion - 1);\n\t}\n}\n\ncreateFolders(state.root, 2); // 3^2\n\nautorun(function() {\n    state.displayRoot = transformFolder(state.root);\n    state.text = stringTransformer(state.displayRoot)\n    console.log(state.text)\n});\n\nstate.root.name = 'wow'; // change folder name\nstate.displayRoot.children[1].collapsed = true; // collapse folder\nstate.filter = \"2\"; // search\nstate.filter = null; // unsearch\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmobxjs%2Fmobx-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmobxjs%2Fmobx-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmobxjs%2Fmobx-utils/lists"}