{"id":30663523,"url":"https://github.com/hichemtab-tech/react-shared-states","last_synced_at":"2026-01-02T20:27:30.906Z","repository":{"id":311516772,"uuid":"1042935245","full_name":"HichemTab-tech/react-shared-states","owner":"HichemTab-tech","description":"Global state made as simple as useState, with zero config, built-in async caching, and automatic scoping.","archived":false,"fork":false,"pushed_at":"2025-08-25T00:29:43.000Z","size":572,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-08-25T01:59:11.081Z","etag":null,"topics":["react","react-hooks","react-state","react-state-management","react-store","shared-state"],"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/HichemTab-tech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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}},"created_at":"2025-08-22T20:35:57.000Z","updated_at":"2025-08-25T00:29:46.000Z","dependencies_parsed_at":"2025-08-25T01:59:14.427Z","dependency_job_id":"4d687977-4a8c-4faf-b2ba-d4fc5ecb59c2","html_url":"https://github.com/HichemTab-tech/react-shared-states","commit_stats":null,"previous_names":["hichemtab-tech/react-shared-states"],"tags_count":3,"template":false,"template_full_name":"HichemTab-tech/npm-package-skeleton","purl":"pkg:github/HichemTab-tech/react-shared-states","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HichemTab-tech%2Freact-shared-states","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HichemTab-tech%2Freact-shared-states/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HichemTab-tech%2Freact-shared-states/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HichemTab-tech%2Freact-shared-states/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HichemTab-tech","download_url":"https://codeload.github.com/HichemTab-tech/react-shared-states/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HichemTab-tech%2Freact-shared-states/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273010994,"owners_count":25030369,"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","status":"online","status_checked_at":"2025-08-31T02:00:09.071Z","response_time":79,"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":["react","react-hooks","react-state","react-state-management","react-store","shared-state"],"created_at":"2025-08-31T17:12:36.944Z","updated_at":"2026-01-02T20:27:30.894Z","avatar_url":"https://github.com/HichemTab-tech.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Shared States\n\n**_Global state made as simple as useState, with zero config, built-in async caching, and automatic scoping._**\n\n![react-shared-states banner](assets/banner.png)\n\n---\n\n[![npm version](https://img.shields.io/npm/v/react-shared-states)](https://www.npmjs.com/package/react-shared-states)\n[![bundle size](https://img.shields.io/bundlephobia/minzip/react-shared-states)](https://bundlephobia.com/package/react-shared-states)\n[![license](https://img.shields.io/github/license/HichemTab-tech/react-shared-states)](LICENSE)\n\n\nTiny, ergonomic, convention‑over‑configuration state, async function, and real-time subscription sharing for React. Global by default, trivially scoped when you need isolation, and opt‑in static APIs when you must touch state outside components. As simple as `useState`, as flexible as Zustand, without boilerplate like Redux.\n\n## 🔥 Why this instead of Redux / Zustand / Context soup?\n* 0 config. Just pick a key: `useSharedState('cart', [])`.\n* Automatic scoping: nearest `SharedStatesProvider` wins; omit it for global.\n* Cross‑tree sharing via named scopes (two providers with same `scopeName` share data) – powerful for portals/modals/micro‑frontends.\n* Async functions become cached shared resources via `useSharedFunction` (built‑in loading, error, results, reentrancy guard, manual or forced refresh).\n* Static APIs (`sharedStatesApi`, `sharedFunctionsApi`) let you prime / read / mutate outside React (SSR, event buses, dev tools, tests).\n* No custom store objects, reducers, actions, selectors, immer, proxies, or serialization hoops.\n* Predictable: key + scope ⇒ value. That’s it.\n\n\n## Install\n\n```sh\nnpm install react-shared-states\n```\nor\n```sh\npnpm add react-shared-states\n```\n\n## 60‑Second TL;DR\n```tsx\nimport { useSharedState } from 'react-shared-states';\n\nfunction A(){\n  const [count, setCount] = useSharedState('counter', 0);\n  return \u003cbutton onClick={()=\u003esetCount(c=\u003ec+1)}\u003eA {count}\u003c/button\u003e;\n}\nfunction B(){\n  const [count] = useSharedState('counter', 0);\n  return \u003cspan\u003eB sees {count}\u003c/span\u003e;\n}\n\nfunction App() {\n  return (\n    \u003c\u003e\n      \u003cA/\u003e\n      \u003cB/\u003e\n    \u003c/\u003e\n  )\n}\n```\nSame key ⇒ same state (global scope by default).\n\nAdd a scope:\n```tsx\nimport { SharedStatesProvider, useSharedState } from 'react-shared-states';\n\nfunction Scoped(){\n  const [count, set] = useSharedState('counter', 0); // isolated inside provider\n  return \u003cbutton onClick={()=\u003eset(c=\u003ec+1)}\u003eScoped {count}\u003c/button\u003e;\n}\n\nfunction App() {\n  return (\n    \u003c\u003e\n      \u003cA/\u003e\n      \u003cB/\u003e\n      \u003cSharedStatesProvider\u003e\n        \u003cScoped/\u003e\n      \u003c/SharedStatesProvider\u003e\n    \u003c/\u003e\n  )\n}\n```\n\n---\n\n\u003e **Tip:** For large apps, you can also use `createSharedState(initialValue, scopeName?)` to create and export reusable shared states. If you specify a `scopeName`, the state will always be found in that scope; otherwise, it defaults to global. This helps avoid key collisions and ensures type safety.\n\n```tsx\nimport { useSharedState } from 'react-shared-states';\nexport const sharedCounter = createSharedState(0);\n\nfunction A(){\n  const [count, setCount] = useSharedState(sharedCounter);\n  return \u003cbutton onClick={()=\u003esetCount(c=\u003ec+1)}\u003eA {count}\u003c/button\u003e;\n}\nfunction B(){\n  const [count] = useSharedState(sharedCounter);\n  return \u003cspan\u003eB sees {count}\u003c/span\u003e;\n}\n\nfunction App() {\n  return (\n    \u003c\u003e\n      \u003cA/\u003e\n      \u003cB/\u003e\n    \u003c/\u003e\n  )\n}\n```\n\nOverride / jump to a named scope explicitly:\n```tsx\nuseSharedState('counter', 0, 'modal'); // 3rd arg is scopeName override\n```\n\nTwo separate trees with the same `SharedStatesProvider scopeName` share their data:\n```tsx\n\u003cSharedStatesProvider scopeName=\"modal\"\u003e\n  \u003cModalContent/\u003e\n\u003c/SharedStatesProvider\u003e\n\u003cPortal target={...}\u003e\n  \u003cSharedStatesProvider scopeName=\"modal\"\u003e\n    \u003cFloatingToolbar/\u003e\n  \u003c/SharedStatesProvider\u003e\n\u003c/Portal\u003e\nfunction App() {\n\n  return (\n    \u003c\u003e\n      \u003cSharedStatesProvider scopeName=\"modal\"\u003e\n        \u003cModalContent/\u003e\n      \u003c/SharedStatesProvider\u003e\n      \u003cPortal target={...}\u003e\n        \u003cSharedStatesProvider scopeName=\"modal\"\u003e\n          \u003cFloatingToolbar/\u003e\n        \u003c/SharedStatesProvider\u003e\n      \u003c/Portal\u003e\n    \u003c/\u003e\n  )\n}\n```\n\nAsync shared function (one fetch, instant reuse when new component mounts):\n```tsx\nimport { useEffect, useState } from 'react';\nimport { useSharedFunction } from 'react-shared-states';\n\n// Any async callback you want to share\nconst fetchCurrentUser = () =\u003e fetch('/api/me').then(r =\u003e r.json());\n\nfunction UserHeader(){\n  const { state, trigger } = useSharedFunction('current-user', fetchCurrentUser);\n  \n  useEffect(() =\u003e {\n    \n    trigger();\n    \n  }, []);\n  \n  if(state.isLoading \u0026\u0026 !state.results) return \u003cp\u003eLoading user...\u003c/p\u003e;\n  \n  if(state.error) return \u003cp style={{color:'red'}}\u003eFailed.\u003c/p\u003e;\n  \n  return \u003ch1\u003e{state.results.name}\u003c/h1\u003e;\n}\n\nfunction UserDetails(){\n  const { state, trigger } = useSharedFunction('current-user', fetchCurrentUser);\n  // This effect will run when the component appears later, but fetch is already cached so trigger does nothing.\n  useEffect(() =\u003e {\n\n    trigger();\n\n  }, []);\n\n  if(state.isLoading \u0026\u0026 !state.results) return \u003cp\u003eLoading user...\u003c/p\u003e; // this will not happen, cuz we already have the shared result\n  if(state.error) return \u003cp style={{color:'red'}}\u003eFailed.\u003c/p\u003e; // this will not happen, cuz we already have the shared result\n  \n  return \u003cpre\u003e{JSON.stringify(state.results, null, 2)}\u003c/pre\u003e;\n}\n\nexport default function App(){\n  const [showDetails, setShowDetails] = useState(false);\n  return (\n    \u003cdiv\u003e\n      \u003cUserHeader/\u003e\n      \u003cbutton onClick={()=\u003esetShowDetails(s=\u003e!s)}\u003e\n        {showDetails ? 'Hide details' : 'Show details'}\n      \u003c/button\u003e\n      {showDetails \u0026\u0026 \u003cUserDetails/\u003e}\n    \u003c/div\u003e\n  );\n}\n\n// If you need to force a refetch somewhere:\n// const { forceTrigger } = useSharedFunction('current-user', fetchCurrentUser);\n// forceTrigger(); // bypass cache \u0026 re-run\n```\n\n\n## Core Concepts\n| Concept                | Summary                                                                                                                                                            |\n|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| Global by default      | No provider necessary. Same key =\u003e shared state.                                                                                                                   |\n| Scoping                | Wrap with `SharedStatesProvider` to isolate. Nearest provider wins.                                                                                                |\n| Named scopes           | `scopeName` prop lets distant providers sync (same name ⇒ same bucket). Unnamed providers auto‑generate a random isolated name.                                    |\n| Manual override        | Third param in `useSharedState` / `useSharedFunction` / `useSharedSubscription` enforces a specific scope ignoring tree search.                                    |\n| Shared functions       | Encapsulate async logic: single flight + cached result + `error` + `isLoading` + opt‑in refresh.                                                                   |\n| Shared subscriptions   | Real-time data streams: automatic cleanup + shared connections + `error` + `isLoading` + subscription state.                                                       |\n| Static APIs            | Access state/functions/subscriptions outside components (`sharedStatesApi`, `sharedFunctionsApi`, `sharedSubscriptionsApi`).                                       |\n| Static/shared creation | Use `createSharedState`, `createSharedFunction`, `createSharedSubscription` to export reusable, type-safe shared resources that persist across `clearAll()` calls. |\n\n\n## Selecting State Slices (`useSharedStateSelector`)\n\nWhen a shared state holds an object, you might only need a small piece of it.\nUsing `useSharedState` will cause your component to re-render whenever *any* part of the object changes.\nTo optimize performance and avoid unnecessary re-renders, you can use the `useSharedStateSelector` hook.\n\nThis hook allows you to subscribe to a specific, memoized slice of a shared state.\nYour component will only re-render if the selected value changes.\nIt uses `react-fast-compare` for efficient deep equality checks.\n\nSignature:\n- `const selectedValue = useSharedStateSelector(key, selector, scopeName?)`\n- `const selectedValue = useSharedStateSelector(sharedStateCreated, selector)`\n\nThe `selector` is a function that receives the full state and returns the desired slice.\n\n### Example: Subscribing to a slice of a user object\n\nImagine a shared state for user settings:\n\n```tsx\n// settingsState.ts\nimport { createSharedState } from 'react-shared-states';\n\nexport const settingsState = createSharedState({\n  theme: 'dark',\n  notifications: {\n    email: true,\n    push: false,\n  },\n  language: 'en',\n});\n```\n\nA component that only cares about the theme can use `useSharedStateSelector` to avoid re-rendering when, for example, the notification settings change.\n\n```tsx\nimport { useSharedState, useSharedStateSelector } from 'react-shared-states';\nimport { settingsState } from './settingsState';\n\nfunction ThemeDisplay() {\n  const theme = useSharedStateSelector(settingsState, (settings) =\u003e settings.theme);\n  \n  console.log('ThemeDisplay renders'); // This will only log when the theme changes\n\n  return \u003cdiv\u003eCurrent theme: {theme}\u003c/div\u003e;\n}\n\nfunction NotificationToggle() {\n  const [settings, setSettings] = useSharedState(settingsState);\n\n  const togglePush = () =\u003e {\n    setSettings(s =\u003e ({\n      ...s,\n      notifications: { ...s.notifications, push: !s.notifications.push }\n    }));\n  };\n\n  return \u003cbutton onClick={togglePush}\u003eToggle Push Notifications\u003c/button\u003e;\n}\n```\n\nIn this example, clicking the `NotificationToggle` button will **not** cause `ThemeDisplay` to re-render.\n\n### A Note on Type Safety\n\nFor the best developer experience and full type safety,\nit is **highly recommended** to use `useSharedStateSelector` with a statically created shared state object from `createSharedState`.\n\nWhen you use a string key directly, the hook cannot infer the type of the state object.\nYou would have to provide the types explicitly as generic arguments, or they will default to `any`.\n\n```tsx\n// Less safe: using a string key\n// You have to specify the types manually.\nconst theme = useSharedStateSelector\u003c{ theme: string; /*...other props*/ }, 'settings', string\u003e(\n  'settings', \n  (settings) =\u003e settings.theme\n);\n\n// Recommended: using a created state object\n// Types are inferred automatically!\nconst theme = useSharedStateSelector(settingsState, (settings) =\u003e settings.theme);\n```\n\n## Shared Async Functions (`useSharedFunction`)\nSignature:\n- `const { state, trigger, forceTrigger, clear } = useSharedFunction(key, asyncFn, scopeName?)`\n- `const { state, trigger, forceTrigger, clear } = useSharedFunction(sharedFunctionCreated)`\n`state` shape: `{ results?: T; isLoading: boolean; error?: unknown }`\n\nSemantics:\n* First `trigger()` (implicit or manual) runs the function; subsequent calls do nothing while loading or after success (cached) unless you `forceTrigger()`.\n* Multiple components with the same key+scope share one execution + result.\n* `clear()` deletes the cache (next trigger re-runs).\n* You decide when to invoke `trigger` (e.g. on mount, on button click, when dependencies change, etc.).\n* Functions created with `createSharedFunction` are **static** and persist across `clearAll()` calls.\n\n### Pattern: lazy load on first render\n```tsx\n// profileFunction.ts\nexport const profileFunction = createSharedFunction((id: string) =\u003e fetch(`/api/p/${id}`).then(r=\u003er.json()));\n\nfunction Profile({id}:{id:string}){\n  const { state, trigger } = useSharedFunction(profileFunction);\n  // ...same as before\n}\n```\n\n### Pattern: always fetch fresh\n```tsx\nconst { state, forceTrigger } = useSharedFunction('server-time', () =\u003e fetch('/time').then(r=\u003er.text()));\nconst refresh = () =\u003e forceTrigger();\n```\n\n\n## Real-time Subscriptions (`useSharedSubscription`)\nPerfect for Firebase listeners, WebSocket connections,\nServer-Sent Events, or any streaming data source that needs cleanup.\n\nSignature:\n- `const { state, trigger, unsubscribe } = useSharedSubscription(key, subscriber, scopeName?)`\n- `const { state, trigger, unsubscribe } = useSharedSubscription(sharedSubscriptionCreated)`\n\n`state` shape: `{ data?: T; isLoading: boolean; error?: unknown; subscribed: boolean }`\n\nThe `subscriber` function receives three callbacks:\n- `set(data)`: Update the shared data\n- `error(error)`: Handle errors \n- `complete()`: Mark loading as complete\n- Returns: Optional cleanup function (called on unsubscribe/unmount)\n\n### Pattern: Firebase Firestore real-time listener\n```tsx\n// userSubscription.ts\nimport { onSnapshot, doc } from 'firebase/firestore';\nimport { createSharedSubscription } from 'react-shared-states';\nimport { db } from './firebase-config';\n\nexport const userSubscription = createSharedSubscription(\n  (set, error, complete) =\u003e {\n    const userDocRef = doc(db, 'users', 'some-user-id');\n    const unsubscribe = onSnapshot(userDocRef, \n      (doc) =\u003e {\n        if (doc.exists()) {\n          set(doc.data());\n        } else {\n          error(new Error('User not found'));\n        }\n        complete();\n      }, \n      (err) =\u003e {\n        error(err);\n        complete();\n      }\n    );\n    return unsubscribe;\n  }\n);\n\nfunction UserProfile({ userId }: { userId: string }) {\n  const { state, trigger, unsubscribe } = useSharedSubscription(userSubscription);\n  // Start listening when component mounts\n  useEffect(() =\u003e {\n    trigger();\n  }, []);\n\n  if (state.isLoading) return \u003cdiv\u003eConnecting...\u003c/div\u003e;\n  if (state.error) return \u003cdiv\u003eError: {state.error.message}\u003c/div\u003e;\n  if (!state.data) return \u003cdiv\u003eUser not found\u003c/div\u003e;\n\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e{state.data.name}\u003c/h1\u003e\n      \u003cp\u003e{state.data.email}\u003c/p\u003e\n      \u003cbutton onClick={unsubscribe}\u003eStop listening\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n### Pattern: WebSocket connection\n```tsx\nimport { useEffect } from 'react';\nimport { useSharedSubscription } from 'react-shared-states';\n\nfunction ChatRoom({ roomId }: { roomId: string }) {\n  const { state, trigger } = useSharedSubscription(\n    `chat-${roomId}`,\n    (set, error, complete) =\u003e {\n      const ws = new WebSocket(`ws://chat-server/${roomId}`);\n      \n      ws.onopen = () =\u003e complete();\n      ws.onmessage = (event) =\u003e {\n        const message = JSON.parse(event.data);\n        set(prev =\u003e [...(prev || []), message]);\n      };\n      ws.onerror = error;\n      \n      return () =\u003e ws.close();\n    }\n  );\n\n  useEffect(() =\u003e {\n    trigger();\n  }, []);\n\n  return (\n    \u003cdiv\u003e\n      {state.isLoading \u0026\u0026 \u003cp\u003eConnecting to chat...\u003c/p\u003e}\n      {state.error \u0026\u0026 \u003cp\u003eConnection failed\u003c/p\u003e}\n      \u003cdiv\u003e\n        {state.data?.map(msg =\u003e (\n          \u003cdiv key={msg.id}\u003e{msg.text}\u003c/div\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n### Pattern: Server-Sent Events\n```tsx\nimport { useEffect } from 'react';\nimport { useSharedSubscription } from 'react-shared-states';\n\nfunction LiveUpdates() {\n  const { state, trigger } = useSharedSubscription(\n    'live-updates',\n    (set, error, complete) =\u003e {\n      const eventSource = new EventSource('/api/live-updates');\n      \n      eventSource.onopen = () =\u003e complete();\n      eventSource.onmessage = (event) =\u003e {\n        set(JSON.parse(event.data));\n      };\n      eventSource.onerror = error;\n      \n      return () =\u003e eventSource.close();\n    }\n  );\n\n  useEffect(() =\u003e {\n    trigger();\n  }, []);\n\n  return \u003cdiv\u003eLatest: {JSON.stringify(state.data)}\u003c/div\u003e;\n}\n```\n\nSubscription semantics:\n* First `trigger()` establishes the subscription; subsequent calls do nothing if already subscribed.\n* Multiple components with the same key+scope share one subscription + data stream.\n* `unsubscribe()` closes the connection and clears the subscribed state.\n* Automatic cleanup on component unmount when no other components are listening.\n* Components mounting later instantly get the latest `data` without re-subscribing.\n\n\n## Static APIs (outside React)\n## Static/Global Shared Resource Creation\n\nFor large apps, you can create and export shared state, function,\nor subscription objects for type safety and to avoid key collisions.\nThis pattern is similar to Zustand or Jotai stores:\n\n```ts\nimport { createSharedState, createSharedFunction, createSharedSubscription, useSharedState, useSharedFunction, useSharedSubscription } from 'react-shared-states';\n\n// Create and export shared resources\nexport const counterState = createSharedState(0);\nexport const fetchUserFunction = createSharedFunction(() =\u003e fetch('/api/me').then(r =\u003e r.json()));\nexport const chatSubscription = createSharedSubscription((set, error, complete) =\u003e {/* ... */});\n\n// Use anywhere in your app\nconst [count, setCount] = useSharedState(counterState);\nconst { state, trigger } = useSharedFunction(fetchUserFunction);\nconst { state, trigger, unsubscribe } = useSharedSubscription(chatSubscription);\n```\nUseful for SSR hydration, event listeners, debugging, imperative workflows.\n\n```ts\nimport { sharedStatesApi, sharedFunctionsApi, sharedSubscriptionsApi } from 'react-shared-states';\n\n// Preload state (global scope by default)\nsharedStatesApi.set('bootstrap-data', { user: {...} });\n\n// Preload state in a named scope\nsharedStatesApi.set('bootstrap-data', { user: {...} }, 'myScope');\n\n// Read later\nconst user = sharedStatesApi.get('bootstrap-data'); // global\nconst userScoped = sharedStatesApi.get('bootstrap-data', 'myScope');\n\n// Inspect all (returns nested object: { [scope]: { [key]: value } })\nconsole.log(sharedStatesApi.getAll());\n\n// Clear all keys in a scope\nsharedStatesApi.clearScope('myScope');\n\n// For shared functions\nconst fnState = sharedFunctionsApi.get('profile-123');\nconst fnStateScoped = sharedFunctionsApi.get('profile-123', 'myScope');\n\n// For shared subscriptions\nconst subState = sharedSubscriptionsApi.get('live-chat');\nconst subStateScoped = sharedSubscriptionsApi.get('live-chat', 'myScope');\n```\n\n## API summary:\n\n| API                      | Methods                                                                                                                                                                                                                     |\n|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `sharedStatesApi`        | `get(key, scopeName?)`, `set(key, val, scopeName?)`, `update(key, updater, scopeName?)`, `has(key, scopeName?)`, `clear(key, scopeName?)`, `clearAll(withoutListeners?, withStatic?)`, `clearScope(scopeName?)`, `getAll()` |\n| `sharedFunctionsApi`     | `get(key, scopeName?)`, `set(key, val, scopeName?)`, `update(key, updater, scopeName?)`, `has(key, scopeName?)`, `clear(key, scopeName?)`, `clearAll(withoutListeners?, withStatic?)`, `clearScope(scopeName?)`, `getAll()` |\n| `sharedSubscriptionsApi` | `get(key, scopeName?)`, `set(key, val, scopeName?)`, `update(key, updater, scopeName?)`, `has(key, scopeName?)`, `clear(key, scopeName?)`, `clearAll(withoutListeners?, withStatic?)`, `clearScope(scopeName?)`, `getAll()` |\n\n`scopeName` defaults to `\"_global\"`. Internally, keys are stored as `${scope}//${key}`. The `.getAll()` method returns a nested object: `{ [scope]: { [key]: value } }`.\n\n\n## Scoping Rules Deep Dive\nResolution order used inside hooks:\n1. Explicit 3rd parameter (`scopeName`)\n2. Nearest `SharedStatesProvider` above the component\n3. The implicit global scope (`_global`)\n\nUnnamed providers auto‑generate a random scope name: each mount = isolated island.\n\nTwo providers sharing the same `scopeName` act as a single logical scope even if they are disjoint in the tree (great for portals / microfrontends).\n\n\n## Comparison Snapshot\n| Criterion      | react-shared-states                      | Redux Toolkit        | Zustand                          |\n|----------------|------------------------------------------|----------------------|----------------------------------|\n| Setup          | Install \u0026 call hook                      | Slice + store config | Create store function            |\n| Global state   | Yes (by key)                             | Yes                  | Yes                              |\n| Scoped state   | Built-in (providers + names + overrides) | Needs custom logic   | Needs multiple stores / contexts |\n| Async helper   | `useSharedFunction` (cache + status)     | Thunks / RTK Query   | Manual or middleware             |\n| Boilerplate    | Near zero                                | Moderate             | Low                              |\n| Static access  | Yes (APIs)                               | Yes (store)          | Yes (store)                      |\n| Learning curve | Minutes                                  | Higher               | Low                              |\n\n\n## Testing Tips\n* Use static APIs to assert state after component interactions.\n* `sharedStatesApi.clearAll(false, true)`, `sharedFunctionsApi.clearAll(false, true)`, `sharedSubscriptionsApi.clearAll(false, true)` in `afterEach` to isolate tests and clear static states.\n* For async functions: trigger once, await UI stabilization, assert `results` present.\n* For subscriptions: mock the subscription source (Firebase, WebSocket, etc.) and verify data flow.\n\n\n## ❓ FAQ\n**Q: How do I reset a single shared state?**  \n`sharedStatesApi.clear('key')`. If the state was created with `createSharedState`, it will reset to its initial value. Otherwise, it will be removed.\n\n**Q: Can I pre-hydrate data on the server?**  \nYes. Call `sharedStatesApi.set(...)` during bootstrap, then first client hook usage will pick it up.\n\n**Q: How do I avoid accidental key collisions?**  \nPrefix keys by domain (e.g. `user:profile`, `cart:items`) or rely on provider scoping.\n\n**Q: Why is my async function not re-running?**  \nIt's cached. Use `forceTrigger()` or `clear()`.\n\n**Q: How do I handle subscription cleanup?**  \nSubscriptions auto-cleanup when no components are listening. You can also manually call `unsubscribe()`.\n\n**Q: Can I use it with Suspense?**  \nCurrently no built-in Suspense wrappers; wrap `useSharedFunction` yourself if desired.\n\n\n## Full API Reference\n### `useSharedState(key, initialValue, scopeName?)`\nReturns `[value, setValue]`.\n\n### `useSharedState(sharedStateCreated)`\nReturns `[value, setValue]`.\n\n### `useSharedStateSelector(key, selector, scopeName?)`\nReturns the selected value.\n\n### `useSharedStateSelector(sharedStateCreated, selector)`\nReturns the selected value.\n\n### `useSharedFunction(key, fn, scopeName?)`\nReturns `{ state, trigger, forceTrigger, clear }`.\n\n### `useSharedFunction(sharedFunctionCreated)`\nReturns `{ state, trigger, forceTrigger, clear }`.\n\n### `useSharedSubscription(key, subscriber, scopeName?)`\nReturns `{ state, trigger, forceTrigger, unsubscribe }`.\n\n### `useSharedSubscription(sharedSubscriptionCreated)`\nReturns `{ state, trigger, forceTrigger, unsubscribe }`.\n\n### `\u003cSharedStatesProvider scopeName?\u003e`\nWrap children; optional `scopeName` (string). If omitted a random unique one is generated.\n\n### Static APIs\n`sharedStatesApi`, `sharedFunctionsApi`, `sharedSubscriptionsApi` (see earlier table).\n\n\n\n## Contributions\n\nWe welcome contributions!\nIf you'd like to improve `react-shared-states`,\nfeel free to [open an issue](https://github.com/HichemTab-tech/react-shared-states/issues) or [submit a pull request](https://github.com/HichemTab-tech/react-shared-states/pulls).\n\n\n## Author\n\n- [@HichemTab-tech](https://www.github.com/HichemTab-tech)\n\n## License\n\n[MIT](https://github.com/HichemTab-tech/react-shared-states/blob/master/LICENSE)\n\n## 🌟 Acknowledgements\n\nInspired by React's built-in primitives and the ergonomics of modern lightweight state libraries.\nThanks to early adopters for feedback.\nIf you'd like to improve `react-shared-states`,\nfeel free to [open an issue](https://github.com/HichemTab-tech/react-shared-states/issues) or [submit a pull request](https://github.com/HichemTab-tech/react-shared-states/pulls).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhichemtab-tech%2Freact-shared-states","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhichemtab-tech%2Freact-shared-states","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhichemtab-tech%2Freact-shared-states/lists"}