{"id":13725332,"url":"https://github.com/monojack/immerx-state","last_synced_at":"2026-02-23T01:03:24.358Z","repository":{"id":42848429,"uuid":"262082783","full_name":"monojack/immerx-state","owner":"monojack","description":"Reactive, fractal and no-nonsense state management with Immer","archived":false,"fork":false,"pushed_at":"2023-01-06T05:16:25.000Z","size":1986,"stargazers_count":21,"open_issues_count":13,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-12T04:45:45.768Z","etag":null,"topics":["fractal","immer","observable","reactive","state"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/monojack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-07T15:06:20.000Z","updated_at":"2024-02-01T03:13:19.000Z","dependencies_parsed_at":"2023-02-05T10:02:20.463Z","dependency_job_id":null,"html_url":"https://github.com/monojack/immerx-state","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fimmerx-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fimmerx-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fimmerx-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fimmerx-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/monojack","download_url":"https://codeload.github.com/monojack/immerx-state/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254488234,"owners_count":22079387,"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":["fractal","immer","observable","reactive","state"],"created_at":"2024-08-03T01:02:19.730Z","updated_at":"2026-02-23T01:03:19.281Z","avatar_url":"https://github.com/monojack.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cimg src=\"images/immerx-state-logo.svg\" height=\"70px\"/\u003e\n\n**Reactive** and **fractal** state management with [Immer](https://github.com/immerjs/immer)\n\n\u003cbr/\u003e\n\n**Table of contents**:\n\n- [`Install`](#install)\n- [`Create`](#create)\n- [`Observe`](#observe)\n- [`Update`](#update)\n- [`Compose`](#compose)\n- [`Combine and compute`](#combine-and-compute)\n- [`Middleware`](#middleware)\n- [`React bindings`](#use-with-react)\n- [`Developer tools`](#developer-tools)\n\n\u003cbr/\u003e\n\n### `Install`\n\n```sh\nnpm install @immerx/state\n```\n\n**Immer** `\u003e= v6.0.0` is a [peer dependency](https://nodejs.org/es/blog/npm/peer-dependencies/) so make sure it's installed.\n\n\u003cbr/\u003e\n\n### `Create`\n\n```js\nimport create from '@immerx/state'\n\nconst state$ = create({ count: 0 })\n```\n\nPretty simple - we create a new state by importing `create` and call it with an _initial state_ value.\n\n\u003cbr/\u003e\n\n### `Observe`\n\n```js\nimport create from '@immerx/state'\n\nconst state$ = create({ count: 0 })\nstate$.subscribe({\n  next: v =\u003e console.log(`Count is: ${v}`),\n})\n// \u003e Count is: 0\n```\n\nOur `state$` is an [observable](https://github.com/tc39/proposal-observable) which we can subscribe to and get notified on every state change.\n\n\u003cbr/\u003e\n\n### `Update`\n\nThe following examples assume that you already know about [Immer](https://github.com/immerjs/immer), what [drafts \u0026 producers](https://immerjs.github.io/immer/docs/produce) are and how to use them.\n\n```js\nimport create from '@immerx/state'\n\nconst state$ = create({ count: 0 })\nstate$.subscribe({\n  next: v =\u003e console.log(`Count is: ${v}`),\n})\n// \u003e Count is: 0\n\nstate$.update(draft =\u003e void draft.count++)\n// \u003e Count is: 1\n```\n\n[![Edit immerx-simple-counter](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/immerx-simple-counter-wrpqb?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\nOur `state$` exposes an `update` method that takes a [curried producer](https://immerjs.github.io/immer/docs/curried-produce) in order to update the underlying state object.\n\n\u003cbr/\u003e\n\n### `Compose`\n\nIn addition to being reactive, `state$` is also [fractal](https://staltz.com/unidirectional-user-interface-architectures.html), which allows as to create and compose smaller and isolated pieces of state where consumers will be notified only for relevant changes. Updates are also isolated so the consumer doesn't need to know anything about the \"global\" state:\n\n```js\nimport create from '@immerx/state'\n\nconst state$ = create({ parent: { child: { name: 'foo' } } })\n\nconst parentState$ = state$.isolate('parent')\nconst childState$ = parentState$.isolate('child')\n\nparentState$.subscribe({\n  next: v =\u003e console.log('parent state: ', v),\n})\n// \u003e parent state: { child: { name: \"foo\" } }\n\nchildState$.subscribe({\n  next: v =\u003e console.log('child state: ', v),\n})\n// \u003e child state: { name: \"foo\" }\n\nstate$.update(draft =\u003e void (draft.otherParent = {}))\n/* nothing logged after this update */\n\nparentState$.update(parentDraft =\u003e void (parentDraft.sibling = 'bar'))\n// \u003e parent state: { child: { name: \"foo\" }, sibling: \"bar\" }\n\nchildState$.update(() =\u003e 'baz')\n// \u003e parent state: { child: { name: \"baz\" }, sibling: \"bar\" }\n// \u003e child state: { name: \"baz\" }\n```\n\n[![Edit immerx-fractal-example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/falling-pine-ndiue?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\n\u003cbr/\u003e\n\n### `Combine and compute`\n\nMore often than we'd like, our consumers need to combine and/or compute different pieces of state, sometimes including parts of its parent state. This type of isolation can be achieved through [`lenses`](https://medium.com/javascript-scene/lenses-b85976cb0534).\n\n\u003e Lenses allow you to abstract state shape behind getters and setters.\n\n```js\nimport create from '@immerx/state'\n\nconst INITIAL_STATE = {\n  user: { name: 'John', remainingTodos: [1, 2] },\n  todos: ['Learn JS', 'Try immerx', 'Read a book', 'Buy milk'],\n}\nconst state$ = create(INITIAL_STATE)\n\nconst user$ = state$.isolate({\n  get: state =\u003e ({\n    ...state.user,\n    remainingTodos: state.user.remainingTodos.map(idx =\u003e state.todos[idx]),\n  }),\n  set: (stateDraft, userState) =\u003e {\n    const idxs = userState.remainingTodos.map(t =\u003e {\n      const idx = stateDraft.todos.indexOf(t)\n      return idx \u003e -1 ? idx : stateDraft.todos.push(t) - 1\n    })\n\n    stateDraft.user = { ...userState, remainingTodos: idxs }\n  },\n})\n\nuser$.subscribe({\n  next: console.log,\n})\n// \u003e { name: \"John\", remainingTodos: [ \"Try immerx\", \"Read a book\" ] }\n\nuser$.update(userDraft =\u003e void userDraft.remainingTodos.push('Say hello'))\n// \u003e { name: \"John\", remainingTodos: [ \"Try immerx\", \"Read a book\", \"Say hello\" ] }\n\nuser$.update(userDraft =\u003e void userDraft.remainingTodos.splice(1, 1))\n// \u003e { name: \"John\", remainingTodos: [ \"Try immerx\", \"Say hello\" ] }\n```\n\n[![Edit immerx-combine-and-compute](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/immerx-combine-and-compute-evqvu?fontsize=14\u0026hidenavigation=1\u0026theme=dark)\n\nLet's quickly explain what's going on here:\n\nObviously, it's a very simple _todos_ app where the state seems to have a normalized shape. We use a lens to provide our consumer with an isolated piece of the state (`user$`) where the `getter` returns the `user` object but also expands `user.remainingTodos`.\n\n```js\nget: state =\u003e ({\n  ...state.user,\n  remainingTodos: state.user.remainingTodos.map(idx =\u003e state.todos[idx]),\n})\n```\n\nThe consumer can manipulate the `remainingTodos` without having any idea that a `todos` list exists in the parent state and that `user.remainingTodos` is actually a list of pointers to entries inside `todos`.\n\nIt can safely push the todo text inside the `remainingTodos`:\n\n```js\nuser$.update(userDraft =\u003e void userDraft.remainingTodos.push('Say hello'))\n```\n\nThe lens' `setter` will take care of updating the parent state while keeping it normalized.\n\n```js\nset: (stateDraft, userState) =\u003e {\n  const idxs = userState.remainingTodos.map(t =\u003e {\n    const idx = stateDraft.todos.indexOf(t)\n    return idx \u003e -1 ? idx : stateDraft.todos.push(t) - 1\n  })\n  stateDraft.user.remainingTodos = idxs\n}\n```\n\n\u003cbr/\u003e\n\n### `Middleware`\n\nMiddleware implementations are based around immer [patches](https://immerjs.github.io/immer/docs/patches). We can register functions (middleware) with **immerx** and they will receive a reference to the `state$` and then be invoked with every patch. Based on how and where something was updated in our state, our middleware can perform side-effect and/or update the state.\n\nThe middleware signature is very simple:\n\n```js\nfunction middleware(state$) {\n  return ({ patches, inversePatches }, state) =\u003e {\n    /**\n     * This function is called for every state update.\n     *\n     * It receives the list of patches/inversePatches\n     * and is closed over the state$ so we can use state$.update()\n     * to update the state in response\n     */\n  }\n}\n```\n\nWe can now pass our middleware to `create` and it'll be registered with **immerx**\n\n```js\nimport create from '@immerx/state'\n\nimport initialState from './state'\nimport middleware from './middleware'\n\ncreate(initialState, [middleware])\n```\n\nCheck out [@immerx/observable](https://github.com/monojack/immerx-observable) - an observable based middleware.\n\n\u003cbr/\u003e\n\n### `Use with React`\n\nCheck out the React bindings at [@immerx/react](https://github.com/monojack/immerx-react)\n\n\u003cbr/\u003e\n\n### `Developer tools`\n\nThe [@immerx/devtools](https://github.com/monojack/immerx-devtools) component provides an overview of all the state changes.\n\nThere is a [Chrome Devtools](https://chrome.google.com/webstore/detail/immerx-devtools/jchcefekimgnfceonnnekpgmphepengp) extension available but it can also be rendered inline, either manually by using the exported **React component**, or as part of an **iframe** if you're not using React.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonojack%2Fimmerx-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmonojack%2Fimmerx-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonojack%2Fimmerx-state/lists"}