{"id":16095493,"url":"https://github.com/superlucky84/state-ref","last_synced_at":"2026-01-29T20:17:22.678Z","repository":{"id":257815491,"uuid":"860571272","full_name":"superlucky84/state-ref","owner":"superlucky84","description":"Universal state management library that can be easily integrated into UI libraries","archived":false,"fork":false,"pushed_at":"2024-12-28T13:44:50.000Z","size":554,"stargazers_count":48,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-12T20:06:23.508Z","etag":null,"topics":["preact","react","solidjs","state","store","svelte","vue"],"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/superlucky84.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2024-09-20T17:29:10.000Z","updated_at":"2025-01-10T02:12:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"68ce4389-f053-4a15-a19c-86e452b93dd3","html_url":"https://github.com/superlucky84/state-ref","commit_stats":{"total_commits":186,"total_committers":2,"mean_commits":93.0,"dds":0.08602150537634412,"last_synced_commit":"6f83e9b0ef73366f3ad9cc9ef1fc2572009539dc"},"previous_names":["superlucky84/state-ref"],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superlucky84%2Fstate-ref","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superlucky84%2Fstate-ref/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superlucky84%2Fstate-ref/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superlucky84%2Fstate-ref/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/superlucky84","download_url":"https://codeload.github.com/superlucky84/state-ref/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248625493,"owners_count":21135513,"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":["preact","react","solidjs","state","store","svelte","vue"],"created_at":"2024-10-09T17:05:30.875Z","updated_at":"2026-01-29T20:17:22.666Z","avatar_url":"https://github.com/superlucky84.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# state-ref\n\n\u003e Universal state management library that can be easily integrated into UI libraries\n\n![sref](https://github.com/user-attachments/assets/93e54d8f-1326-482c-b2f6-e9822386425b)\n\n`StateRef` is a state management library focused on data immutability.\n\nIt combines proxies and the functional programming lens pattern to efficiently and safely access and modify deeply structured data.\n\nIt provides more direct and fine-grained state management compared to other types of state management libraries.\n\nIt is also designed for easy integration with other UI libraries. We provide code snippets for connecting with React, Preact, Vue, Svelte, Solid, and Lithent , and users can also create their own connection snippets.\n\n\n* Table of Contents\n    * [Basic Usage](https://github.com/superlucky84/state-ref/?tab=readme-ov-file#basic-usage)\n\n    * [Using with UI Libraries](https://github.com/superlucky84/state-ref/?tab=readme-ov-file#using-with-ui-libraries)\n        * [Usage with React](https://github.com/superlucky84/state-ref/tree/main/packages/connect-react)\n        * [Usage with Preact](https://github.com/superlucky84/state-ref/tree/main/packages/connect-preact)\n        * [Usage with Svelte](https://github.com/superlucky84/state-ref/tree/main/packages/connect-svelte)\n        * [Usage with Vue](https://github.com/superlucky84/state-ref/tree/main/packages/connect-vue)\n        * [Usage with Solid](https://github.com/superlucky84/state-ref/tree/main/packages/connect-solid)\n        * Usage with Lithent\n\n    * [Advanced Usage](https://github.com/superlucky84/state-ref/?tab=readme-ov-file#advanced-usage)\n        * [combineWatch](https://github.com/superlucky84/state-ref/?tab=readme-ov-file#combinewatch)\n        * [createComputed](https://github.com/superlucky84/state-ref/?tab=readme-ov-file#createcomputed)\n        * [supports flux like state management](https://github.com/superlucky84/state-ref/?tab=readme-ov-file#supports-flux-like-state-management)\n\n\n    * [Acknowledgements](https://github.com/superlucky84/state-ref/?tab=readme-ov-file#acknowledgements)\n    * [npm](https://github.com/superlucky84/state-ref/?tab=readme-ov-file#npm)\n\n\n## Basic Usage\n\nThe basic principle is that the subscription function only reacts to values retrieved through `.value`, and when a value is assigned with `.value=`, the subscription function is triggered if the value is already subscribed.\n\n### Understanding References: Inner vs Outer\n\nWhen you register a subscription function via `watch`, it is executed once initially to collect dependencies. The second argument `isFirst` indicates whether this is the first run.\n\n```typescript\nconst subscribeCallback = (innerRef, isFirst) =\u003e {\n  const matrixCount = innerRef.rowCount.value * innerRef.columnCount.value;\n  console.log(matrixCount);\n};\n\n// outerRef: Bound to subscribeCallback\nconst outerRef = watch(subscribeCallback);\n\n// anotherRef: Not bound to any subscription\nconst anotherRef = watch();\n```\n\n**Key Points:**\n- Both `innerRef` (callback argument) and `outerRef` (return value) are **the same reference**\n- Both are **bound to the subscription** - accessing `.value` from either registers tracking\n- `anotherRef` is **unbound** - accessing `.value` doesn't register any tracking\n\n```typescript\n// This WON'T trigger subscribeCallback when etcCount changes\nconst anotherRef = watch();\n\nconst subscribeCallback = (innerRef, isFirst) =\u003e {\n  const result =\n    innerRef.rowCount.value *        // Tracked\n    innerRef.columnCount.value *     // Tracked\n    anotherRef.etcCount.value;       // NOT tracked\n  console.log(result);\n};\n\nconst outerRef = watch(subscribeCallback);\n\n// Both trigger subscribeCallback\nanotherRef.rowCount.value = 10;\nanotherRef.columnCount.value = 5;\n\n// This doesn't trigger subscribeCallback\nanotherRef.etcCount.value = 2;\n```\n\nBesides the `innerRef` reference object used inside the subscription function, as seen in the previous example, the `outerRef` returned by `watch` allows access outside the subscription callback. This design makes it easier to integrate with components in a UI library.\n\nTo illustrate, I’ll use my project, the component-based UI library [lithent](https://github.com/superlucky84/lithent), as an example.\n\n```typescript\nconst Component = mount((renew) =\u003e {\n    let count = 1;\n  \n    const change = () =\u003e {\n        count += 1;\n        renew();\n    };\n  \n    return () =\u003e \u003cbutton onClick={change}\u003e{count}\u003c/button\u003e;\n});\n```\n\n`mount` is a function that creates a component, and it provides a `renew` function as the first argument to the function it consumes.\n\nThe `renew` function requests an update for the component. In the example below, it increments the `count` value by 1 and then re-renders the component.\n\n\u003e In Lithent, calling update functions like `renew` is generally seen as an anti-pattern, but this approach was adopted to keep state management simple and practical using native closures.\n\nIf you want to share store values using `state-ref` instead of the component’s internal `count` state, you can do so as follows.\n\n```typescript\n\nconst watch = createStore(1);\n\nconst Component = mount((renew) =\u003e {\n    count countRef = watch(renew);\n  \n    const change = () =\u003e {\n      countRef.value += 1;\n    };\n  \n    return () =\u003e \u003cbutton onClick={change}\u003e{count.value}\u003c/button\u003e;\n});\n```\n\nBy returning a proxy reference externally via `watch`, you can easily collect the subscription points outside of the subscription function, making it useful in various scenarios.\n\nUsing `outerRef`, you can effortlessly connect components with state.\n\nBuilding on this feature, you can also connect state easily in `React` and `Preact` using simple snippets:\n\n* [react snippet](https://github.com/superlucky84/state-ref/blob/main/packages/connect-react/src/index.ts)\n* [preact snippet](https://github.com/superlucky84/state-ref/blob/main/packages/connect-preact/src/index.ts)\n\n\n### cancel subscription\n\nIf you want to cancel the subscription, use `abortController` as shown in the example below.\n\n```typescript\nconst abortController = new AbortController();\n\nwatch((stateRef) =\u003e {\n    console.log(\n        \"Changed John's Second House Color\",\n        stateRef.john.house[1].color.value\n    );\n\n    return abortController.signal;\n});\n\nabortController.abort(); // run abort\n```\n\n**Primitive types** like numbers or strings can also be handled easily. Here's how:\n\n```typescript\nconst watch = createStore\u003cnumber\u003e(3);\n\nwatch((stateRef) =\u003e {\n    console.log(\n        \"Changed Privitive Number\",\n        stateRef.value\n    );\n});\n\n```\n\n## Using with UI Libraries\n\n### Usage with React\n\n* It can be easily integrated with other UI libraries, and below is an example using React.\n\n* Create the store and pass the `watch` to `connectReact` to create a state that can be used in components.\n\n\u003e profileStore.ts\n```typescript\nimport { connectReact } from \"@stateref/connect-react\";\n// import { connectPreact } from \"@stateref/connect-preact\"; // for Preact\nimport { createStore } from \"state-ref\";\n\ntype Info = { age: number; house: { color: string; floor: number }[] };\ntype People = { john: Info; brown: Info; sara: Info };\n\nconst watch = createStore\u003cPeople\u003e({\n    john: {\n        age: 20,\n        house: [\n            { color: \"red\", floor: 5 },\n            { color: \"red\", floor: 5 },\n        ],\n    },\n    brown: { age: 26, house: [{ color: \"red\", floor: 5 }] },\n    sara: { age: 26, house: [{ color: \"red\", floor: 5 }] },\n});\n\nexport const useProfileStore = connectReact(watch);\n```\n\n\u003e UserComponent.tsx\n\n```tsx\nimport { useProfileStore } from 'profileStore';\n\nfunction UserComponent() {\n  const {\n    john: { age: ageRef },\n  } = useProfileStore();\n\n  const increaseAge = () =\u003e {\n    ageRef.value += 1;\n  };\n\n  return (\n    \u003cbutton onClick={increaseAge}\u003e\n        john's age: {ageRef.value}\n    \u003c/button\u003e;\n  );\n}\n```\n\nIn the example above, `useProfileStore` directly returns `stateRef`, allowing easy access to values and modification through `copyOnWrite`.\n\nYou can create your own custom connection pattern by referring to the [connectReact implementation code](https://github.com/superlucky84/state-ref/blob/main/packages/connect-react/src/index.ts).\n\n### Usage with ...\n\n* [React](https://www.npmjs.com/package/@stateref/connect-react)\n* [Preact](https://www.npmjs.com/package/@stateref/connect-preact)\n* [Svelte](https://www.npmjs.com/package/@stateref/connect-svelte)\n* [Vue](https://www.npmjs.com/package/@stateref/connect-vue)\n* [Solid](https://www.npmjs.com/package/@stateref/connect-solid)\n* Lithent\n\n    ```tsx\n    import { mount, h } from 'lithent';\n    import { watch } from 'profileStore';\n\n    const UserComponent = mount(renew =\u003e {\n        const { john: { age: ageRef } } = watch(renew);\n        const increaseAge = () =\u003e { ageRef.value += 1 };\n\n        return () =\u003e \u003cbutton onClick={increaseAge}\u003e john's age: {ageRef.value} \u003c/button\u003e;\n    });\n    ```\n\n\n\n## Advanced Usage\n\n### combineWatch\n\n`combineWatch` is a helper function that **observes multiple `Watch` instances together** and produces a new `Watch` that delivers their **combined values as a tuple-like structure**.\n\nUnlike `createComputed`, which produces a **single derived value**, `combineWatch` focuses on **grouping multiple watches** so you can react to changes from any of them in a **single subscription**.\nWhen combined multiple times, the structure naturally **nests**, allowing you to build **hierarchical watch compositions**.\n\n#### Basic Usage\n\n```typescript\nimport { createStore, combineWatch } from \"state-ref\";\n\nconst countWatch = createStore\u003cnumber\u003e(100);\nconst textWatch = createStore\u003cstring\u003e(\"hello\");\n\n// Combine multiple watches into one\nconst combinedCountTextWatch = combineWatch([countWatch, textWatch] as const);\n\ncombinedCountTextWatch(([countRef, textRef], isFirst) =\u003e {\n  console.log(\"Combined Watches:\", countRef.value, textRef.value, isFirst);\n});\n\n// Update a watch\nconst countRef = countWatch();\ncountRef.value = 200; \n// → triggers callback with [200, \"hello\"]\n```\n\n#### Nested Combination\n\nYou can **nest `combineWatch`** to observe more complex structures:\n\n```typescript\nconst countWatch = createStore\u003cnumber\u003e(100);\nconst textWatch = createStore\u003cstring\u003e(\"hello\");\nconst toggleWatch = createStore\u003cboolean\u003e(false);\n\n// Combine countWatch and textWatch\nconst combinedCountTextWatch = combineWatch([countWatch, textWatch] as const);\n\n// Nest the combined watch with toggleWatch\nconst combinedAllWatch = combineWatch([combinedCountTextWatch, toggleWatch] as const);\n\ncombinedAllWatch(([countTextRef, toggleRef], isFirst) =\u003e {\n  const [countRef, textRef] = countTextRef;\n  console.log(\"Nested Watches:\", countRef.value, textRef.value, toggleRef.value, isFirst);\n});\n```\n\n### createComputed \n\n`createComputed` is a helper function that combines multiple watches to produce a new computed (derived) value, and executes a specified callback function whenever that computed value changes.\n\nA Watch created with `createComputed` can be used just like any other watch, including in integrations such as `connectReact` or `connectPreact`.\n\nBelow is a simple usage example.\n\n```typescript\nimport { createStore, createComputed } from \"state-ref\";\nimport type { StateRefStore, Watch } from \"state-ref\";\n\ntype Info = { age: number; house: { color: string; floor: number }[] };\n\nconst watch1 = createStore\u003cInfo\u003e(\n    { age: 10, house: [{ color: \"blue\", floor: 7 }] },\n);\nconst watch2 = createStore\u003cnumber\u003e(20);\n\nconst computedWatch = creatComputed\u003c[Watch\u003cInfo\u003e, Watch\u003cnumber\u003e], number\u003e([watch1, watch2], ([ref1, ref2]) =\u003e {\n    return ref1.age.value + ref2.value;\n});\n\n\n// To subscribe\ncomputedWatch((stateRef) =\u003e {\n    console.log(\n        \"Changed Computed Value\",\n        stateRef.value\n    );\n});\n\n// Change value\nconst computedRef = watch2();\ncomputedRef.value = 30;\n\n// Connect another ui library\nconst useComputedValue = connectReact(computedWatch);\n```\n\n### Supports Flux-like State Management\n   \nIf users prefer to manage state using a centralized store pattern, `state-ref` provides flexibility with the `createStoreManualSync` function. This mode makes it easier to implement centralized patterns like `Flux`.\n\nBelow is a simple `Flux-like` example using `createStoreManualSync` with React.\n\n#### profileStore\n\n`createStoreManualSync` returns `updateRef` and `sync`, along with `watch`.\n\nIn the default mode, values can be modified through the references created by `watch`. However, in `manualSync` mode, values cannot be modified via `watch`.\n\nTo update values, you must use `updateRef`. To propagate the changes to subscribed code (and trigger subscription callbacks), you can manually execute the `sync` function at your desired time.\n\n```typescript\nimport { createStoreManualSync } from \"state-ref\";\n\ntype Info = { age: number; house: { color: string; floor: number }[] };\ntype People = { john: Info; brown: Info; sara: Info };\n\nconst { watch, updateRef, sync } = createStoreManualSync\u003cPeople\u003e({\n    john: { age: 20, house: [ { color: \"red\", floor: 5 }] },\n    brown: { age: 26, house: [{ color: \"red\", floor: 5 }] },\n});\n\nexport const useProfileStore = connectReact(watch);\n\n// Action to change John's age\nexport const changeJohnAge = (newAge: number) =\u003e {\n    updateRef.john.age.value = newAge;\n    sync();\n};\n\n// Action to change Brown's first house info\nexport const changeBrownFirstHouseInfo = (\n    firstHouseInfo = { color: 'blue', floor: 7 }\n) =\u003e {\n    updateRef.brown.house[0].value = firstHouseInfo;\n    sync();\n};\n```\n\n#### UserComponent.tsx\n\nValues can only be updated through actions created by `profileStore`. Any attempt to modify the values in other ways will result in an error.\n\n```tsx\nimport { useProfileStore, changeJohnAge } from 'profileStore';\n\nfunction UserComponent() {\n  // The stateRef received via watch or the values received via connect are for reference only\n  // (direct modification is not allowed).\n  const {\n    john: { age: ageRef },\n  } = useProfileStore();\n\n  const increaseAge = () =\u003e {\n    // An error occurs if you attempt to modify 'ageRef' directly.\n    // ageRef.value += 1; // \n\n    // You must modify the reference through the action's updateRef.\n    // Afterward, the subscribed code will synchronize via the sync function\n    // (triggering subscription callbacks).\n    changeJohnAge(ageRef.value + 1);\n  };\n\n  return (\n    \u003cbutton onClick={increaseAge}\u003e\n        john's age: {ageRef.value}\n    \u003c/button\u003e;\n  );\n}\n```\n\n## Acknowledgements\n\nI would like to extend my gratitude to the following people and projects:\n\n- **[Juho Vepsäläinen](https://survivejs.com)**: Thank you for the [insightful interview](https://survivejs.com/blog/state-ref-interview/) and featuring me on your blog. Your work and contributions to the JavaScript community have been a great source of inspiration.\n\n## npm\n* [state-ref](https://www.npmjs.com/package/state-ref)\n* [connect-react](https://www.npmjs.com/package/@stateref/connect-react)\n* [connect-preact](https://www.npmjs.com/package/@stateref/connect-preact)\n* [connect-solid](https://www.npmjs.com/package/@stateref/connect-solid)\n* [connect-svelte](https://www.npmjs.com/package/@stateref/connect-svelte)\n* [connect-vue](https://www.npmjs.com/package/@stateref/connect-vue)\n* [lithent](https://www.npmjs.com/package/lithent)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperlucky84%2Fstate-ref","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuperlucky84%2Fstate-ref","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperlucky84%2Fstate-ref/lists"}