{"id":44670524,"url":"https://github.com/daltonlet/scope-state","last_synced_at":"2026-02-15T02:01:17.844Z","repository":{"id":298983329,"uuid":"998602225","full_name":"daltonlet/scope-state","owner":"daltonlet","description":"The simplest global state system for React.  A tiny reactive state manager with global reach and local clarity. Built for modern React. No stale bugs. No mental gymnastics. Full support for storage persistence, auto-optimization, and more.","archived":false,"fork":false,"pushed_at":"2025-06-14T00:13:17.000Z","size":109,"stargazers_count":2,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-22T07:00:03.088Z","etag":null,"topics":["mutable-state","react","reactjs","signals","state-management"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/daltonlet.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-09T00:21:40.000Z","updated_at":"2025-06-17T03:03:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"2bf39d71-dd18-448a-8315-58ad8450d563","html_url":"https://github.com/daltonlet/scope-state","commit_stats":null,"previous_names":["daltonlet/scope-state"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/daltonlet/scope-state","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daltonlet%2Fscope-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daltonlet%2Fscope-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daltonlet%2Fscope-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daltonlet%2Fscope-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/daltonlet","download_url":"https://codeload.github.com/daltonlet/scope-state/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daltonlet%2Fscope-state/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29465397,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-15T01:01:38.065Z","status":"online","status_checked_at":"2026-02-15T02:00:07.449Z","response_time":118,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["mutable-state","react","reactjs","signals","state-management"],"created_at":"2026-02-15T02:00:41.476Z","updated_at":"2026-02-15T02:01:17.834Z","avatar_url":"https://github.com/daltonlet.png","language":"TypeScript","readme":"# Scope State\n_The simplest global state system for React._\n\nA tiny reactive state manager with global reach and local clarity. Built for modern React. No stale bugs. No mental gymnastics. Full support for storage persistence, auto-optimization, and more.\n\nBuilt for developers who hate Redux and love clarity.\n\n## Why Scope State?\n\nScope State gives you **global reactive state** with:\n- **Zero reducers, zero contexts**\n- **Zero boilerplate**\n- **Zero spreads, zero selectors**\n- **No need for `setState`**\n- And most importantly — **no stale value bugs**\n\nJust write:\n\n```tsx\nimport { useScope, configure } from 'scope-state';\n\nconst $ = configure({\n  initialState: {\n    counter: {\n      count: 0,\n    },\n    user: { \n      name: 'John', \n      age: 30\n    }\n  }\n});\n\nconst CounterComponent = () =\u003e {\n  \n  const count = useScope(() =\u003e $.counter.count); // subscribe to only the count\n\n  const handleCountIncrement = () =\u003e {\n    // You can mutate the state directly\n    $.counter.count++;\n    // OR use the updater function\n    $.counter.$update(\"count\", (count) =\u003e count + 1);\n  }\n\n  const resetCount = () =\u003e {\n    // use the $set method to replace the entire object\n    $.counter.$set({ count: 0 }); \n    // OR use the $merge method to merge the new properties with the existing ones\n    $.counter.$merge({ count: 0 });\n    // OR use the $reset method to reset the entire object\n    $.counter.$reset();\n    // OR literally replace the count with a direct assignment\n    $.counter.count = 0;\n  }\n  \n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e{count}\u003c/h1\u003e\n      \u003cbutton onClick={handleCountIncrement}\u003e+ Increment\u003c/button\u003e\n      \u003cbutton onClick={resetCount}\u003e🔄 Reset\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nThat's it. It tracks dependencies automatically and re-renders only what changed.\n\n\n## What Makes It Different?\n\n- **Fully reactive** — inspired by proxies, not reducers\n- **Intuitive reads and writes** — no `.get()` or `.set()` syntax hell\n- **Mutate like it's a regular object or ref** — works with objects, arrays, numbers, everything\n- **Fine-grained tracking** — no wasted renders\n- **Built-in debug tools** \n- **Feels like magic**\n- **Read and set states *independently* —** outside of functional components or custom hooks\n\n\n## Getting Started\n\n### Installation\n\n```bash\nnpm install scope-state\n```\n\n### Quick Start\n\n```tsx\n// store.ts\nimport { configure } from 'scope-state';\nexport const $ = configure({\n  initialState: {\n    user: { name: 'John', age: 30 }\n  }\n});\n```\n\n```tsx\n// UserProfile.tsx\nimport { useScope } from 'scope-state';\nimport { $ } from './store';\n\nexport const UserProfile = () =\u003e {\n  const name = useScope(() =\u003e $.user.name);\n  return \u003ch1\u003e{name}\u003c/h1\u003e;\n}\n```\n\n### Basic Usage\n\n```tsx\nimport { useScope, configure } from 'scope-state';\n\n// RECOMMENDED: Configure with your initial state\n// It's best to configure your initial store in a separate file.\n// The usage of the dollar sign ($) is optional; just a way to keep it brief\n// and easy to identify.\n\nexport const $ = configure({\n  initialState: {\n    user: { name: 'John', age: 30 },\n    todos: [],\n    theme: 'dark'\n  }\n});\n\n// Use in components\nimport { useScope } from 'scope-state';\n\nexport const UserProfileComponent = () =\u003e {\n\n  const user = useScope(() =\u003e $.user);\n  \n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e{user.name}\u003c/h1\u003e\n      \u003cbutton \n        onClick={() =\u003e {\n          user.age += 1\n        }}\n      \u003e\n          Age: {user.age}\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n\nconst TodoList = () =\u003e {\n  const todos = useScope(() =\u003e $.todos);\n  \n  const addTodo = () =\u003e {\n    $.todos.push({ id: Date.now(), text: 'New todo', done: false });\n  };\n  \n  return (\n    \u003cdiv\u003e\n      {todos.map(todo =\u003e (\n        \u003cdiv key={todo.id}\u003e{todo.text}\u003c/div\u003e\n      ))}\n      \u003cbutton onClick={addTodo}\u003eAdd Todo\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n## 📚 API Reference\n\n### Core Functions\n\n#### `useScope(selector)`\nSubscribe to reactive state changes.\n\n```tsx\n// Subscribe to entire object\nconst user = useScope(() =\u003e $.user);\n\n// Subscribe to specific property\nconst userName = useScope(() =\u003e $.user.name);\n\n// Subscribe to computed value\nconst isAdmin = useScope(() =\u003e $.user.role === 'admin');\n\n// Subscribe to array\nconst todos = useScope(() =\u003e $.todos);\n```\n\n#### `configure(options)`\nConfigure Scope State with custom settings.\n\n```tsx\nimport { configure, presets } from 'scope-state';\n\n// Use a preset\nconfigure(presets.production());\n\n// Custom configuration\nconst $ = configure({\n  initialState: { /* your state */ },\n  monitoring: { enabled: true },\n  proxy: { maxDepth: 3 }\n});\n\n// Then access any item in your state by scoping it using the main hook:\nconst restaurants = useScope(() =\u003e $.restaurants || []) // optional fallback\n```\n\n---\n\n### Object Methods\n\nAll objects in the store have these reactive methods:\n\n#### `$merge(newProps)`\nMerge new properties without removing existing ones.\n\n```tsx\n$.user.$merge({ name: 'John' }); // Updates only name\n```\n\n#### `$set(newProps)`\nReplace object with new properties.\n\n```tsx\n$.user.$set({ name: 'John', age: 25 }); // Replaces entire user object\n```\n\n#### `raw()`\nGet plain, serializable JavaScript object without any function references (the reactivity methods removed).\n\n```tsx\nconst plainUser = $.user.raw(); \n```\n_This is helpful when you need to serialize the state for storage, API calls, or debugging. Otherwise, it's not necessary._\n\n---\n\n### Array Methods\n\nArrays have enhanced methods that trigger reactivity:\n\n```tsx\ntodos.push({ id: 1, text: 'Buy milk' });  // This will trigger a re-render\n\ntodos.splice(0, 1);                       // This will trigger a re-render\n\n$.todos = [/* new array */]               // You can also directly assign a new array to the property in the global state itself ($).\n```\n\n### Utility Functions\n\nCreate reactive local state (not global).\n\n```tsx\nimport { useLocal } from 'scope-state';\n\nfunction MyComponent() {\n  const localState = useLocal({ count: 0 });\n  \n  return (\n    \u003cbutton onClick={() =\u003e localState.count + 1}\u003e\n      Count: {localState.count}\n    \u003c/button\u003e\n  );\n}\n```\n\n\n\n## Configuration\n\n\n\n### Presets\n\n```tsx\nimport { configure, presets } from 'scope-state';\n\n// Development: Enhanced debugging\nconfigure(presets.development());\n\n// Production: Optimized performance\nconfigure(presets.production());\n\n// Minimal: Memory-constrained environments\nconfigure(presets.minimal());\n\n// Full-featured: All features enabled\nconfigure(presets.full());\n```\n\n\n\n### Custom Configuration\n\n```tsx\nconfigure({\n  initialState: {\n    // Your app's initial state\n  },\n  proxy: {\n    maxDepth: 5, // How deep to proxy objects\n    smartArrayTracking: true, // Optimize array operations\n  },\n  monitoring: {\n    enabled: true, // Enable debug logging\n    verboseLogging: false, // Detailed logs\n    autoLeakDetection: true, // Detect memory leaks\n  },\n  persistence: {\n    enabled: true, // Enable state persistence\n    paths: ['user', 'settings.theme'], // Which paths to persist (leave as undefined to persist all paths)\n  }\n});\n```\n\n\n\n## Philosophy\n\nReact's core primitives like `useState`, `useReducer`, and `useContext` work well for many use cases.\n\nBut when your app grows in complexity…\n- deeply nested objects,\n- shared state across pages,\n- state persistence,\n- or fine-grained reactivity,\n\nsuddenly you're spending time wiring reducers, spreading props, memoizing selectors, and debugging re-renders.\n\nScope State simplifies that.\n\nYou write and read state _directly_, just like a `ref` or a signal, but with full reactivity, automatic tracking, and global accessibility.\n\n\nThis library was built out of frustration with every other state system:\n- **Redux** is too bloated\n- **Recoil** is too verbose  \n- **Zustand** still forces manual updates\n- **Legend State** is performant (and deserves significant respect) but has a higher learning curve and confusing API. It's simply ahead of its time.\n\nThe mental model is simple: write and read directly. Like `useRef`, but global, reactive, and tracked.\n\n## Best Practices\n\n### 1. Keep Selectors Simple\n\n```tsx\n// ✅ Good\nconst name = useScope(() =\u003e $.user.name);\n\n// ❌ Avoid complex computations in selectors\nconst expensiveData = useScope(() =\u003e $.data.map(/* heavy computation */));\n\n// Instead...\nconst data = useScope(() =\u003e $.data);\nconst expensiveCalculation = useMemo(() =\u003e data.map(/* heavy computation */), [data])\n```\n\n\n\n### 2. Use Direct Assignment \u0026 Flexible Methods for Updates\n\n```tsx\n// Option 1: Direct assignment\n$.user.name = \"John\";\n\n$.todos.push({ title: \"Do Laundry\", date: new Date().toISOString() })\n\n// Option 2: Shallow merge a new value without changing existing properties\n$.user.$merge({ name: 'John' });\n\n// Option 3: Use the updater function [NEW!]\n$.user.$update(\"age\", (age) =\u003e age + 1);\n\n```\n\n\n\n### 3. Configure Persistence (Optional)\n\n```tsx\n// ✅ Configure before your app starts\nconfigure(presets.production());\n\nfunction App() {\n  // Your app components\n}\n```\n\n\n## Created by Dalton Letorney\n\nIf you like this, feel free to star the repo! If you love it, use it in production. If it breaks, open a PR so we can make this even more epic.\n\n\n---\n\n\n**Scope State is minimal by design** — the goal is not to reinvent React, but to make it finally feel clean again.\n\n\n## License\n\nMIT © Dalton Letorney","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaltonlet%2Fscope-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaltonlet%2Fscope-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaltonlet%2Fscope-state/lists"}