{"id":22940540,"url":"https://github.com/d8corp/watch-state","last_synced_at":"2026-05-05T05:31:13.589Z","repository":{"id":39596362,"uuid":"279949627","full_name":"d8corp/watch-state","owner":"d8corp","description":"The simplest watcher of your state.","archived":false,"fork":false,"pushed_at":"2023-06-25T17:27:59.000Z","size":4454,"stargazers_count":1,"open_issues_count":15,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-21T09:03:58.063Z","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/d8corp.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}},"created_at":"2020-07-15T18:42:19.000Z","updated_at":"2022-01-07T12:22:42.000Z","dependencies_parsed_at":"2025-02-07T13:17:37.114Z","dependency_job_id":"1737b400-1dbb-473d-9041-ed1e69c98d19","html_url":"https://github.com/d8corp/watch-state","commit_stats":null,"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d8corp%2Fwatch-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d8corp%2Fwatch-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d8corp%2Fwatch-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d8corp%2Fwatch-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/d8corp","download_url":"https://codeload.github.com/d8corp/watch-state/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246709923,"owners_count":20821297,"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-12-14T13:23:11.300Z","updated_at":"2026-05-05T05:31:13.582Z","avatar_url":"https://github.com/d8corp.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cbr\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/d8corp/watch-state\"\u003e\n    \u003cimg width=\"200\" height=\"200\" src=\"https://raw.githubusercontent.com/d8corp/watch-state/v3.3.1/img/logo.svg\" alt=\"watch-state logo by Mikhail Lysikov\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003ewatch-state\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003eCANT inc. Reactive State Engine\u003c/p\u003e\n\n\u003cbr\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ctable\u003e\n    \u003ccol width=\"140\" align=\"center\"\u003e\n    \u003ctr\u003e\u003ctd align=\"center\"\u003e\n      \u003ca href=\"https://raw.githubusercontent.com/d8corp/watch-state/v3.3.1/img/speed.test.png\" target=\"_blank\"\u003e\n        \u003cimg width=\"64\" height=\"64\" src=\"https://raw.githubusercontent.com/d8corp/watch-state/v3.3.1/img/fast.svg\" alt=\"watch-state fast\"\u003e\n      \u003c/a\u003e\n      \u003cbr\u003e\n      \u003cb\u003eFast\u003c/b\u003e\n      \u003cbr\u003e\n      One of the fastest\n    \u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cspan\u003e\n      \u003ca href=\"https://bundlephobia.com/result?p=watch-state\" target=\"_blank\"\u003e\n        \u003cimg width=\"64\" height=\"64\" src=\"https://raw.githubusercontent.com/d8corp/watch-state/v3.3.1/img/light.svg\" alt=\"watch-state Light\"\u003e\n      \u003c/a\u003e\n      \u003cbr\u003e\n      \u003cb\u003eLight\u003c/b\u003e\n      \u003cbr\u003e\n      Less than 1 KB gzip\n    \u003c/span\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cspan\u003e\n      \u003ca href=\"https://d8corp.github.io/watch-state/coverage/lcov-report\" target=\"_blank\"\u003e\n        \u003cimg width=\"64\" height=\"64\" src=\"https://raw.githubusercontent.com/d8corp/watch-state/v3.3.1/img/smart.svg\" alt=\"watch-state smart\"\u003e\n      \u003c/a\u003e\n      \u003cbr\u003e\n      \u003cb\u003eSmart\u003c/b\u003e\n      \u003cbr\u003e\n      Steady architecture\n    \u003c/span\u003e\u003c/td\u003e\u003c/tr\u003e\n  \u003c/table\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/watch-state\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/watch-state.svg\" alt=\"watch-state npm\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmtrends.com/watch-state\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dm/watch-state.svg\" alt=\"watch-state downloads\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/d8corp/watch-state/tree/master/release\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://packagephobia.com/badge?p=watch-state\" alt=\"watch-state install size\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://cdn.jsdelivr.net/npm/watch-state\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.badgesize.io/https:/cdn.jsdelivr.net/npm/watch-state?compression=gzip\" alt=\"watch-state gzip size\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.typescriptlang.org\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/types/watch-state\" alt=\"TypeScript\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://packagequality.com/#?package=watch-state\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://packagequality.com/shield/watch-state.svg\" alt=\"watch-state quality\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/d8corp/watch-state/blob/master/LICENSE\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/l/watch-state\" alt=\"watch-state license\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/d8corp/watch-state/blob/master/CHANGELOG.md\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Changelog-⋮-brightgreen\" alt=\"watch-state changelog\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://d8corp.github.io/watch-state/coverage/lcov-report\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://github.com/d8corp/watch-state/actions/workflows/tests.yml/badge.svg\" alt=\"watch-state tests\"\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\u003cbr\u003e\n\n`watch-state` is a **lightweight, high-performance reactive state engine** designed to power UI frameworks — **or replace them.**\n\n- **Fast** — One of the fastest reactive libraries ([see benchmarks](#performance))\n- **Light** — Less than 1 KB minzip\n- **Zero-dependency** — No external packages required\n- **Code splitting by design** — Decentralized state architecture, each page loads only the states it uses\n- **Auto-subscription** — Dependencies tracked automatically, no manual subscriptions\n- **Dynamic subscriptions** — Conditional watchers auto-subscribe/unsubscribe based on reactive conditions\n- **Type-safe** — Full TypeScript support with type inference\n- **Memory-safe** — Automatic cleanup on destroy\n- **Lazy computation** — Compute executes only when accessed\n- **No Proxy** — Supports old browsers (Firefox 45+, Safari 9+)\n- **Framework-agnostic** — Business logic lives outside components, reusable across any framework or vanilla JS\n\nUse it as the core state layer in your own framework, embed it in React components, or build a full UI — **no JSX, no virtual DOM, no framework required**.\n\nBorn while working on [@innet/dom](https://www.npmjs.com/package/@innet/dom).\n\n[![stars](https://img.shields.io/github/stars/d8corp/watch-state?style=social)](https://github.com/d8corp/watch-state/stargazers)\n[![watchers](https://img.shields.io/github/watchers/d8corp/watch-state?style=social)](https://github.com/d8corp/watch-state/watchers)\n\n## Browser Support\n\n### Desktop\n\n| \u003cimg src=\"https://cdn.worldvectorlogo.com/logos/firefox.svg\" width=\"18\" valign=\"middle\"\u003e Firefox | \u003cimg src=\"https://cdn.worldvectorlogo.com/logos/chrome.svg\" width=\"18\" valign=\"middle\"\u003e Chrome | \u003cimg src=\"https://cdn.worldvectorlogo.com/logos/safari-3.svg\" width=\"18\" valign=\"middle\"\u003e Safari | \u003cimg src=\"https://cdn.worldvectorlogo.com/logos/opera-2.svg\" width=\"18\" valign=\"middle\"\u003e Opera | \u003cimg src=\"https://cdn.worldvectorlogo.com/logos/microsoft-edge-1.svg\" width=\"18\" valign=\"middle\"\u003e Edge |\n|:-------:|:------:|:------:|:-----:|:----:|\n| 45+     | 49+    | 9+     | 36+   | 13+  |\n\n### Mobile\n\n| \u003cimg src=\"https://cdn.worldvectorlogo.com/logos/firefox.svg\" width=\"18\" valign=\"middle\"\u003e Firefox | \u003cimg src=\"https://cdn.worldvectorlogo.com/logos/chrome.svg\" width=\"18\" valign=\"middle\"\u003e Chrome | \u003cimg src=\"https://cdn.worldvectorlogo.com/logos/safari-3.svg\" width=\"18\" valign=\"middle\"\u003e Safari | \u003cimg src=\"https://cdn.worldvectorlogo.com/logos/opera-2.svg\" width=\"18\" valign=\"middle\"\u003e Opera |\n|:-------:|:------:|:------:|:-----:|\n| 87+     | 90+    | 9+     | 62+   |\n\n*You can transpile the code to support browsers older than listed above, but performance will decrease.*\n\n## Index\n\n\u003csup\u003e**[ [Install](#install) ]**\u003c/sup\u003e  \n\u003csup\u003e**[ [Usage](#usage) ]** [Simple example](#simple-example) • [Example Vanilla JS](#example-vanilla-js) • [Example React](#example-react) • [Example Innet](#example-innet)\u003c/sup\u003e  \n\u003csup\u003e**[ [Watch](#watch) ]** [Force update of Watch](#force-update-of-watch) • [Destroy Watch](#destroy-watch) • [Deep/Nested Watchers](#deepnested-watchers)\u003c/sup\u003e  \n\u003csup\u003e**[ [State](#state) ]** [Get or Set value](#get-or-set-value) • [State.set](#stateset) • [Force update of State](#force-update-of-state) • [Raw value](#raw-value) • [Initial value](#initial-value) • [Reset value](#reset-value)\u003c/sup\u003e  \n\u003csup\u003e**[ [Compute](#compute) ]** [Lazy computation](#lazy-computation) • [Force update of Compute](#force-update-of-compute) • [Destroy Compute](#destroy-compute)\u003c/sup\u003e  \n\u003csup\u003e**[ [Utils](#utils) ]** [onDestroy](#ondestroy) • [callEvent](#callevent) • [createEvent](#createevent) • [unwatch](#unwatch)\u003c/sup\u003e  \n\u003csup\u003e**[ [Typescript](#typescript) ]** [State type inference](#state-type-inference) • [Compute type inference](#compute-type-inference)\u003c/sup\u003e  \n\u003csup\u003e**[ [Performance](#performance) ]**\u003c/sup\u003e\n\n## Install\n###### [🏠︎](#index) / Install [↓](#usage)\n\nnpm\n```shell\nnpm i watch-state\n```\n\nyarn\n```shell\nyarn add watch-state\n```\n\nhtml\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/watch-state\"\u003e\u003c/script\u003e\n```\n\n[minified on GitHub](https://github.com/d8corp/watch-state/blob/master/release/index.min.js)\n\n## Usage\n###### [🏠︎](#index) / Usage [↑](#install) [↓](#watch)\n\n\u003csup\u003e[Simple example](#simple-example) • [Example Vanilla JS](#example-vanilla-js) • [Example React](#example-react) • [Example Innet](#example-innet)\u003c/sup\u003e\n\nThe library is based on the core concepts of `Observable` (something that can be observed) and `Observer` (something that can observe). On top of these concepts, the core classes `State`, `Compute`, and `Watch` are built according to the following scheme:\n\n```\n   ┌────────────┐ ┌─────────────┐\n   │ Observable │ │  Observer   │\n   │ (abstract) │ │ (interface) │\n   └──────┬─────┘ └──────┬──────┘  \n     ┌────┴─────┐ ┌──────┴───┐\n┌────┴────┐ ┌───┴─┴───┐ ┌────┴────┐\n│  State  │ │ Compute │ │  Watch  │\n└─────────┘ └─────────┘ └─────────┘\n```\n\n### Simple example\n###### [🏠︎](#index) / [Usage](#usage) / Simple example [↓](#example-vanilla-js)\n\nYou can create an instance of `State` and **watch** its **value**.\n\n```javascript\nimport { Watch, State } from 'watch-state'\n\nconst count = new State(0)\n\nnew Watch(() =\u003e console.log(count.value))\n// logs: 0\n\ncount.value++\n// logs: 1\n\ncount.value++\n// logs: 2\n```\n\n### Example Vanilla JS\n###### [🏠︎](#index) / [Usage](#usage) / Example Vanilla JS [↑](#simple-example) [↓](#example-react)\n\nSimple reactive state without build tools or framework dependencies.\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003ctitle\u003eCounter\u003c/title\u003e\n    \u003cscript src=\"https://cdn.jsdelivr.net/npm/watch-state\"\u003e\u003c/script\u003e\n    \u003cscript type=\"module\"\u003e\n      const { State, Watch } = WatchState\n\n      const count = new State(0)\n      const button = document.createElement('button');\n\n      document.body.appendChild(button);\n\n      new Watch(() =\u003e {\n        button.innerText = count.value\n      })\n\n      button.addEventListener('click', () =\u003e {\n        count.value++\n      })\n    \u003c/script\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Example React\n###### [🏠︎](#index) / [Usage](#usage) / Example React [↑](#example-vanilla-js) [↓](#example-innet)\n\n[@watch-state/react](https://www.npmjs.com/package/@watch-state/react) provides hooks that automatically subscribe React components to state changes and re-renders only when needed.\n\n```tsx\nimport { State } from 'watch-state'\nimport { useObservable } from '@watch-state/react'\n\nconst $count = new State(0)\n\nconst increase = () =\u003e {\n  $count.value++\n}\n\nexport function CountButton () {\n  const count = useObservable($count)\n\n  return \u003cbutton onClick={increase}\u003e{count}\u003c/button\u003e\n}\n```\n\n### Example Innet\n###### [🏠︎](#index) / [Usage](#usage) / Example Innet [↑](#example-react)\n\n[@innet/dom](https://www.npmjs.com/package/@innet/dom) automatically watches accessed states and **updates only changed DOM content** — **no full re-renders**.\n\n```tsx\nimport { State } from 'watch-state'\n\nconst count = new State(0)\n\nconst increase = () =\u003e {\n  count.value++\n}\n\nexport function CountButton () {\n  return \u003cbutton onClick={increase}\u003e{count}\u003c/button\u003e\n}\n```\n\n## Watch\n###### [🏠︎](#index) / Watch [↑](#usage) [↓](#state)\n\n\u003csup\u003e[Force update of Watch](#force-update-of-watch) • [Destroy Watch](#destroy-watch) • [Deep/Nested watchers](#deepnested-watchers)\u003c/sup\u003e\n\n`Watch` accepts a **reaction** as its first argument and executes it when any accessed state changes.\nState accessed inside a reaction is **auto-subscribed** — no manual registration needed.\n\n```ts\nconst state = new State(0)\n\nconst reaction = () =\u003e {\n console.log(state.value)\n // auto-subscribes to state\n}\n\nnew Watch(reaction)\n// logs: 0\n\nstate.value = 1 // triggers reaction\n// logs: 1\n```\n\n### Force update of Watch\n###### [🏠︎](#index) / [Watch](#watch) / Force update of Watch [↓](#destroy-watch)\n\nYou can run a reaction even when its states are not updated.\n\n```typescript\nconst count = new State(0)\n\nconst watcher = new Watch(() =\u003e {\n  console.log(count.value)\n})\n// logs: 0\n\nwatcher.update()\n// logs: 0\n```\n\n### Destroy Watch\n###### [🏠︎](#index) / [Watch](#watch) / Destroy Watch [↑](#force-update-of-watch) [↓](#deepnested-watchers)\n\nYou can stop watching by `destroy` method of `Watch`.\n\n```javascript\nconst count = new State(0)\n\nconst watcher = new Watch(() =\u003e {\n  console.log(count.value)\n})\n// logs: 0\n\ncount.value++\n// logs: 1\n\nwatcher.destroy()\n\ncount.value++\n// nothing happens\n```\n\n### Deep/Nested Watchers\n###### [🏠︎](#index) / [Watch](#watch) / Deep/Nested Watchers [↑](#destroy-watch)\n\nEach `Watch` **independently tracks only states accessed within its reaction**.\nNested watchers created inside parent watchers form a **dependency tree** with separate reactivity.\n\n```javascript\nconst watching = new State(true)\nconst state = new State(0)\n\nnew Watch(() =\u003e {\n  console.log('Root Render')\n\n  if (watching.value) {\n    new Watch(() =\u003e {\n      console.log(`Deep Render: ${state.value}`)\n    })\n  }\n})\n// logs: Root Render, Deep Render: 0\n\nstate.value++\n// logs: Deep Render: 1  (only deep watcher reacts)\n\nwatching.value = false\n// logs: Root Render     (deep watcher destroyed)\n\nstate.value++\n// nothing happens       (no active deep watcher)\n```\n\n## State\n###### [🏠︎](#index) / State [↑](#watch) [↓](#compute)\n\n\u003csup\u003e[Get or Set value](#get-or-set-value) • [State.set](#stateset) • [Force update of State](#force-update-of-state) • [Raw value](#raw-value) • [Initial value](#initial-value) • [Reset value](#reset-value)\u003c/sup\u003e\n\n**Reactive primitive** that holds a value and automatically notifies all subscribers when it changes.\n\n### Get or Set value\n###### [🏠︎](#index) / [State](#state) / Get or Set value [↓](#stateset)\n\nReading `.value` inside reaction **auto-subscribes** to changes. Writing `.value` **triggers all reactions**.\n\n```ts\nconst count = new State(0)\n\nnew Watch(() =\u003e console.log(count.value))\n// auto-subscribes and logs 0\n\ncount.value++ // triggers: logs 1\n```\n\n### State.set\n###### [🏠︎](#index) / [State](#state) / State.set [↑](#get-or-set-value) [↓](#force-update-of-state)\n\n`State.set` mirrors the behavior of the value setter but returns `void`.\nIt is useful as a shorthand in arrow functions: `() =\u003e state.set(nextValue)` instead of `() =\u003e { state.value = nextValue }`.\n\nNote: `state.set` cannot be used as a standalone function; `const set = state.set` is not supported.\n\n```ts\nconst count = new State(0)\n\n// Subscribing\nnew Watch(() =\u003e console.log(count.value))\n// logs: 0\n\ncount.set(1)\n// logs: 1\n```\n\n### Force update of State\n###### [🏠︎](#index) / [State](#state) / Force update of State [↑](#stateset) [↓](#raw-value)\n\nYou can run reactions of a state with `update` method.\n\n```ts\n// Create state\nconst log = new State\u003cnumber[]\u003e([])\n\n// Subscribe to changes\nnew Watch(() =\u003e console.log(log.value)) // logs: []\n\n// Modify the array\nlog.value.push(1) // no logs\nlog.value.push(2) // no logs\n\n// Update value\nlog.update() // logs: [1, 2]\n```\n\n### Raw value\n###### [🏠︎](#index) / [State](#state) / Raw value [↑](#force-update-of-state) [↓](#initial-value)\n\n `raw` returns the current value but does not subscribe to changes — unlike `value`.\n\n```ts\nconst foo = new State(0)\nconst bar = new State(0)\n\n new Watch(() =\u003e console.log(foo.value, bar.raw))\n// logs: 0, 0\n\nfoo.value++ // logs: 1, 0\nbar.value++ // no logs\nfoo.value++ // logs: 2, 1\n```\n\n### Initial value\n###### [🏠︎](#index) / [State](#state) / Initial value [↑](#raw-value) [↓](#reset-value)\n\n`initial` stores the initial value passed to the constructor.\n Useful for checking if the state has been modified by comparing `state.initial === state.raw`.\n\n```ts\nconst count = new State(0)\n\nconsole.log(count.initial)\n// logs: 0\n\ncount.value = 5\nconsole.log(count.initial === count.raw)\n// logs: false\n\ncount.reset()\nconsole.log(count.initial === count.raw)\n// logs: true\n```\n\n### Reset value\n###### [🏠︎](#index) / [State](#state) / Reset value [↑](#initial-value)\n\n`reset()` restores the state to its initial value.\nTriggers watchers only if the current value differs from the initial value.\n\n```ts\nconst count = new State(0)\n\nnew Watch(() =\u003e console.log(count.value))\n// logs: 0\n\ncount.value = 5\n// logs: 5\n\ncount.reset()\n// logs: 0\n\ncount.reset()\n// no logs (value already 0)\n```\n\n## Compute\n###### [🏠︎](#index) / Compute [↑](#state) [↓](#utils)\n\n\u003csup\u003e[Lazy computation](#lazy-computation) • [Force update of Compute](#force-update-of-compute) • [Destroy Compute](#destroy-compute)\u003c/sup\u003e\n\n`Compute` accepts a **reaction** as its first argument and represents a reactive value returned by the reaction.\nIt creates a **derived state** that automatically tracks dependencies and caches the result.\n\n### Lazy computation\n###### [🏠︎](#index) / [Compute](#compute) / Lazy computation [↓](#force-update-of-compute)\n\n`Compute` doesn't execute immediately — waits for `.value` access.  \nDependencies (`State.value` reads inside reaction) auto-subscribe like `Watch`.\n\n```javascript\nconst name = new State('Foo')\nconst surname = new State('Bar')\n\nconst fullName = new Compute(() =\u003e (\n  `${name.value} ${surname.value[0]}` // auto-subscribes to name+surname\n))\n// NO COMPUTATION YET — lazy!\n\nnew Watch(() =\u003e {\n  console.log(fullName.value) // FIRST ACCESS → computes!\n})\n// logs: 'Foo B'\n\nsurname.value = 'Baz' // surname[0] still \"B\"\n// nothing happens\n\nsurname.value = 'Quux' // surname[0] = \"Q\"\n// logs: 'Foo Q'\n```\n\n### Force update of Compute\n###### [🏠︎](#index) / [Compute](#compute) / Force update of Compute [↑](#lazy-computation) [↓](#destroy-compute)\n\nYou can run a reaction of a compute with `update` method.\n\n```ts\nconst items = new State([])\n\nconst itemCount = new Compute(() =\u003e {\n  console.log('Recomputing length...')\n  return items.value.length\n})\n\nnew Watch(() =\u003e console.log('Watcher sees:', itemCount.value))\n// logs: Recomputing length...\n// logs: Watcher sees: 0\n\nitems.value.push('apple')\n// Array reference SAME → NO recompute!\n\nitemCount.update()\n// logs: Recomputing length...\n// logs: Watcher sees: 1\n```\n\n### Destroy Compute\n###### [🏠︎](#index) / [Compute](#compute) / Destroy Compute [↑](#force-update-of-compute)\n\nYou can stop watching by `destroy` method of `Compute`.\n\n```ts\nconst user = new State({ name: 'Alice', age: 30 })\n\nconst userName = new Compute(() =\u003e {\n  console.log('Computing')\n  return user.value.name.toUpperCase()\n})\n\nnew Watch(() =\u003e console.log(userName.value))\n// logs: Computing\n// logs: ALICE\n\nuser.value = { name: 'Mike', age: 32 }\n// logs: Computing\n// logs: MIKE\n\nuserName.destroy()\n\nuser.value = { name: 'Bob', age: 31 }\n// nothing happens — fully disconnected!\n```\n\n## Utils\n###### [🏠︎](#index) / Utils [↑](#compute) [↓](#typescript)\n\n\u003csup\u003e[onDestroy](#ondestroy) • [callEvent](#callevent) • [createEvent](#createevent) • [unwatch](#unwatch)\u003c/sup\u003e\n\n### onDestroy\n###### [🏠︎](#index) / [Utils](#utils) / onDestroy [↓](#callevent)\n\nYou can subscribe on destroy or update of watcher\n\n```javascript\nconst count = new State(0)\n\nconst watcher = new Watch(() =\u003e {\n  console.log('count', count.value)\n  // the order does not matter\n  onDestroy(() =\u003e console.log('destructor'))\n})\n// logs: 'count', 0\n\ncount.value++\n// logs: 'destructor'\n// logs: 'count', 1\n\nwatcher.destroy()\n// logs: 'destructor'\n\ncount.value++\n// nothing happens\n```\n\n### callEvent\n###### [🏠︎](#index) / [Utils](#utils) / callEvent [↑](#ondestroy) [↓](#createevent)\n\nYou can immediately execute a reactive effect with `callEvent`.\n\n`callEvent` batches all state updates inside the callback and triggers watchers only once at the end.\n\n```ts\nconst a = new State(0)\nconst b = new State(0)\n\nnew Watch(() =\u003e {\n console.log(a.value, b.value)\n})\n// logs: 0, 0\n\na.value = 1\n// logs: 1, 0\n\nb.value = 1\n// logs: 1, 1\n\ncallEvent(() =\u003e {\n a.value = 2\n b.value = 2\n})\n// logs: 2, 2\n```\n\n`callEvent` returns exactly what your callback returns — TypeScript infers the correct type automatically.\n\n```ts\nconst count = new State(0)\n\nnew Watch(() =\u003e console.log(count.value))\n// logs: 0\n\nconst prev = callEvent(() =\u003e count.value++)\n// logs: 1\n\nconsole.log(prev)\n// logs: 0\n```\n\n### createEvent\n###### [🏠︎](#index) / [Utils](#utils) / createEvent [↑](#callevent) [↓](#unwatch)\n\nYou can create a reusable event function with `createEvent`.\n\nLike `callEvent`, it batches state updates and triggers watchers only once after execution.\n\n```typescript\nimport { State, createEvent } from 'watch-state'\n\nconst count = new State(0)\nconst increase = createEvent(() =\u003e count.value++)\n\nnew Watch(() =\u003e console.log(count.value))\n// logs: 0\n\nincrease()\n// logs: 1\n\nincrease()\n// logs: 2\n```\n\n### unwatch\n###### [🏠︎](#index) / [Utils](#utils) / unwatch [↑](#createevent)\n\nYou can disable automatic state subscriptions with `unwatch`.\n\n```ts\nimport { State, Watch, unwatch } from 'watch-state'\n\nconst count = new State(0)\n\nnew Watch(() =\u003e {\n  console.log(unwatch(() =\u003e count.value++))\n})\n// logs: 0\n\ncount.value++\n// logs: 1\n\nconsole.log(count.value)\n// logs: 2\n```\n\n## Typescript\n###### [🏠︎](#index) / Typescript [↑](#utils) [↓](#performance)\n\n### State type inference\n###### [🏠︎](#index) / [Typescript](#typescript) / State type inference [↓](#compute-type-inference)\n\n**Type inference from initial value:**  \nType is automatically inferred from the initial value passed to the constructor — no generic needed.\n```typescript\nconst count = new State(0) // State\u003cnumber\u003e\n\ncount.value = 'str' // error: number expected\n```\n\n**Without initial value:**  \nWhen using a generic without an initial value, `initial` is `undefined`, which may conflict with strict types.\n\n```typescript\nconst value = new State\u003cstring\u003e()\n// value.initial is undefined (not string)\n\n// To allow undefined in type:\nconst maybe = new State\u003cstring | undefined\u003e()\n```\n\n**State as a type annotation:**  \nWithout a generic, `State` defaults to `State\u003cunknown\u003e`, which accepts any value type.\n\n```typescript\nconst foo: State = new State(0)\n\nfoo.value = 'str' // ok (unknown allows any)\nfoo.value = true  // ok\n\n// Specify generic for type safety:\nconst bar: State\u003cnumber\u003e = new State(0)\n\nbar.value = 'str' // error\n```\n\n### Compute type inference\n###### [🏠︎](#index) / [Typescript](#typescript) / Compute type inference [↑](#state-type-inference)\n\n**Type inferred from function return:**  \nType is automatically inferred from the function's return value — no generic needed.\n```typescript\nconst fullName = new Compute(() =\u003e `${firstName.value} ${lastName.value}`)\n// Compute\u003cstring\u003e — no generic needed\n\nconst length = new Compute(() =\u003e items.value.length)\n// Compute\u003cnumber\u003e\n```\n\n**Explicit generic (usually not needed):**  \nExplicit generics are rarely needed since types are inferred. Use only when you want to enforce a specific type.\n```typescript\nnew Compute\u003cstring\u003e(() =\u003e false) // error: boolean not assignable to string\n```\n\n**Destroyed Compute and undefined:**  \n`Compute.value` is typed as the function return type, but if you access `.value` after `destroy()` (before any computation ran), it returns `undefined`.\n```typescript\nconst computed = new Compute(() =\u003e expensiveCalculation())\n\ncomputed.destroy()\nconsole.log(computed.value) // undefined (but typed as return type)\n```\n\nThis is intentional — accessing destroyed observers is rare and shouldn't require `undefined` checks in normal code.\n\n## Performance\n###### [🏠︎](#index) / Performance [↑](#typescript)\n\nYou can check a performance test with **[MobX](https://www.npmjs.com/package/mobx)**, **[Effector](https://www.npmjs.com/package/effector)**, **[Storeon](https://www.npmjs.com/package/storeon)**, **[Nano Stores](https://www.npmjs.com/package/nanostores)**, **[Mazzard](https://www.npmjs.com/package/mazzard)** and **[Redux](https://www.npmjs.com/package/redux)**.\nClone the repo, install packages and run this command\n```shell\nnpm run speed\n```\n\n## Links\nYou can find more tools [here](https://www.npmjs.com/search?q=%40watch-state)\n\n## Issues\nIf you find a bug or have a suggestion, please file an issue on [GitHub](https://github.com/d8corp/watch-state/issues)\n\n[![issues](https://img.shields.io/github/issues-raw/d8corp/watch-state)](https://github.com/d8corp/watch-state/issues)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd8corp%2Fwatch-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fd8corp%2Fwatch-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd8corp%2Fwatch-state/lists"}