@@ -48,7 +48,6 @@ function B(){
4848}
4949
5050function App() {
51-
5251 return (
5352 <>
5453 <A />
@@ -69,7 +68,6 @@ function Scoped(){
6968}
7069
7170function App() {
72-
7371 return (
7472 <>
7573 <A />
@@ -82,6 +80,33 @@ function App() {
8280}
8381```
8482
83+ ---
84+
85+ > ** 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.
86+
87+ ``` tsx
88+ import { useSharedState } from ' react-shared-states' ;
89+ export const sharedCounter = createSharedState (0 );
90+
91+ function A(){
92+ const [count, setCount] = useSharedState (sharedCounter );
93+ return <button onClick = { ()=> setCount (c => c + 1 )} >A { count } </button >;
94+ }
95+ function B(){
96+ const [count] = useSharedState (sharedCounter );
97+ return <span >B sees { count } </span >;
98+ }
99+
100+ function App() {
101+ return (
102+ <>
103+ <A />
104+ <B />
105+ </>
106+ )
107+ }
108+ ```
109+
85110Override / jump to a named scope explicitly:
86111``` tsx
87112useSharedState (' counter' , 0 , ' modal' ); // 3rd arg is scopeName override
@@ -173,19 +198,22 @@ export default function App(){
173198
174199
175200## 🧠 Core Concepts
176- | Concept | Summary |
177- | ----------------------| ---------------------------------------------------------------------------------------------------------------------------------|
178- | Global by default | No provider necessary. Same key => shared state. |
179- | Scoping | Wrap with ` SharedStatesProvider ` to isolate. Nearest provider wins. |
180- | Named scopes | ` scopeName ` prop lets distant providers sync (same name ⇒ same bucket). Unnamed providers auto‑generate a random isolated name. |
181- | Manual override | Third param in ` useSharedState ` / ` useSharedFunction ` / ` useSharedSubscription ` enforces a specific scope ignoring tree search. |
182- | Shared functions | Encapsulate async logic: single flight + cached result + ` error ` + ` isLoading ` + opt‑in refresh. |
183- | Shared subscriptions | Real-time data streams: automatic cleanup + shared connections + ` error ` + ` isLoading ` + subscription state. |
184- | Static APIs | Access state/functions/subscriptions outside components (` sharedStatesApi ` , ` sharedFunctionsApi ` , ` sharedSubscriptionsApi ` ). |
201+ | Concept | Summary |
202+ | ------------------------| ---------------------------------------------------------------------------------------------------------------------------------|
203+ | Global by default | No provider necessary. Same key => shared state. |
204+ | Scoping | Wrap with ` SharedStatesProvider ` to isolate. Nearest provider wins. |
205+ | Named scopes | ` scopeName ` prop lets distant providers sync (same name ⇒ same bucket). Unnamed providers auto‑generate a random isolated name. |
206+ | Manual override | Third param in ` useSharedState ` / ` useSharedFunction ` / ` useSharedSubscription ` enforces a specific scope ignoring tree search. |
207+ | Shared functions | Encapsulate async logic: single flight + cached result + ` error ` + ` isLoading ` + opt‑in refresh. |
208+ | Shared subscriptions | Real-time data streams: automatic cleanup + shared connections + ` error ` + ` isLoading ` + subscription state. |
209+ | Static APIs | Access state/functions/subscriptions outside components (` sharedStatesApi ` , ` sharedFunctionsApi ` , ` sharedSubscriptionsApi ` ). |
210+ | Static/shared creation | Use ` createSharedState ` , ` createSharedFunction ` , ` createSharedSubscription ` to export reusable, type-safe shared resources. |
185211
186212
187213## 🏗️ Sharing State (` useSharedState ` )
188- Signature: ` const [value, setValue] = useSharedState(key, initialValue, scopeName?); `
214+ Signature:
215+ - ` const [value, setValue] = useSharedState(key, initialValue, scopeName?) `
216+ - ` const [value, setValue] = useSharedState(sharedStateCreated) `
189217
190218Behavior:
191219* First hook call (per key + scope) seeds with ` initialValue ` .
@@ -194,35 +222,44 @@ Behavior:
194222* React batching + equality check: listeners fire only when the value reference actually changes.
195223
196224### Examples
197- 1 . Global theme
225+ 1 . Global theme (recommended for large apps)
198226 ``` tsx
199- const [theme, setTheme] = useSharedState (' theme' , ' light' );
227+ // themeState.ts
228+ export const themeState = createSharedState (' light' );
229+ // In components
230+ const [theme, setTheme] = useSharedState (themeState );
200231 ```
2012322. Isolated wizard progress
202233 ` ` ` tsx
234+ const wizardProgress = createSharedState(0);
203235 <SharedStatesProvider>
204236 <Wizard/>
205237 </SharedStatesProvider>
238+ // In Wizard
239+ const [step, setStep] = useSharedState(wizardProgress);
206240 ` ` `
2072413. Forcing cross ‑portal sync
208242 ` ` ` tsx
243+ const navState = createSharedState('closed', 'nav');
209244 <SharedStatesProvider scopeName="nav" children={<PrimaryNav/>} />
210245 <Portal>
211246 <SharedStatesProvider scopeName="nav" children={<MobileNav/>} />
212247 </Portal>
248+ // In both navs
249+ const [navOpen, setNavOpen] = useSharedState(navState);
213250 ` ` `
2142514. Overriding nearest provider
215252 ` ` ` tsx
216253 // Even if inside a provider, this explicitly binds to global
217- const [flag, setFlag] = useSharedState('feature-x-enabled', false, '_global');
254+ const globalFlag = createSharedState(false, '_global');
255+ const [flag, setFlag] = useSharedState(globalFlag);
218256 ` ` `
219257
220258
221259## ⚡ Shared Async Functions (` useSharedFunction ` )
222260Signature :
223- ` ` ` ts
224- const { state, trigger, forceTrigger, clear } = useSharedFunction(key, asyncFn, scopeName?);
225- ` ` `
261+ - ` const { state, trigger, forceTrigger, clear } = useSharedFunction(key, asyncFn, scopeName?) `
262+ - ` const { state, trigger, forceTrigger, clear } = useSharedFunction(sharedFunctionCreated) `
226263` state ` shape : ` { results?: T; isLoading: boolean; error?: unknown } `
227264
228265Semantics :
@@ -233,12 +270,12 @@ Semantics:
233270
234271### Pattern : lazy load on first render
235272` ` ` tsx
273+ // profileFunction.ts
274+ export const profileFunction = createSharedFunction((id: string) => fetch( ` / api / p / $ {id }` ).then(r=>r.json()));
275+
236276function Profile({id}:{id:string}){
237- const { state, trigger } = useSharedFunction( ` profile - $ {id }` , () => fetch( ` / api / p / $ {id }` ).then(r=>r.json()));
238-
239- if(!state.results && !state.isLoading) trigger();
240- if(state.isLoading) return <p>Loading...</p>;
241- return <pre>{JSON.stringify(state.results,null,2)}</pre>
277+ const { state, trigger } = useSharedFunction(profileFunction);
278+ // ...same as before
242279}
243280` ` `
244281
@@ -254,9 +291,8 @@ Perfect for Firebase listeners, WebSocket connections,
254291Server - Sent Events , or any streaming data source that needs cleanup .
255292
256293Signature :
257- ` ` ` ts
258- const { state, trigger, unsubscribe } = useSharedSubscription(key, subscriber, scopeName?);
259- ` ` `
294+ - ` const { state, trigger, unsubscribe } = useSharedSubscription(key, subscriber, scopeName?) `
295+ - ` const { state, trigger, unsubscribe } = useSharedSubscription(sharedSubscriptionCreated) `
260296
261297` state ` shape : ` { data?: T; isLoading: boolean; error?: unknown; subscribed: boolean } `
262298
@@ -268,36 +304,19 @@ The `subscriber` function receives three callbacks:
268304
269305### Pattern : Firebase Firestore real - time listener
270306` ` ` tsx
271- import { useEffect } from 'react';
307+ // userSubscription.ts
272308import { onSnapshot, doc } from 'firebase/firestore';
273- import { useSharedSubscription } from 'react-shared-states';
274- import { db } from './firebase-config'; // your Firebase config
309+ import { createSharedSubscription } from 'react-shared-states';
310+ import { db } from './firebase-config';
275311
276- function UserProfile({ userId }: { userId: string }) {
277- const { state, trigger, unsubscribe } = useSharedSubscription(
278- ` user - $ {userId }` ,
279- async (set, onError, onCompletion) => {
280- const userRef = doc(db, 'users', userId);
281-
282- // Set up the real-time listener
283- const unsubscribe = onSnapshot(
284- userRef,
285- (snapshot) => {
286- if (snapshot.exists()) {
287- set({ id: snapshot.id, ...snapshot.data() });
288- } else {
289- set(null);
290- }
291- },
292- onError,
293- onCompletion
294- );
295-
296- // Return cleanup function
297- return unsubscribe;
298- }
299- );
312+ export const userSubscription = createSharedSubscription(
313+ async (set, onError, onCompletion) => {
314+ // ...same as before
315+ }
316+ );
300317
318+ function UserProfile({ userId }: { userId: string }) {
319+ const { state, trigger, unsubscribe } = useSharedSubscription(userSubscription);
301320 // Start listening when component mounts
302321 useEffect(() => {
303322 trigger();
@@ -395,6 +414,23 @@ Subscription semantics:
395414
396415
397416## 🛰️ Static APIs (outside React)
417+ ## 🏛️ Static/Global Shared Resource Creation
418+
419+ For large apps, you can create and export shared state, function, or subscription objects for type safety and to avoid key collisions. This pattern is similar to Zustand or Jotai stores:
420+
421+ ``` ts
422+ import { createSharedState , createSharedFunction , createSharedSubscription , useSharedState , useSharedFunction , useSharedSubscription } from ' react-shared-states' ;
423+
424+ // Create and export shared resources
425+ export const counterState = createSharedState (0 );
426+ export const fetchUserFunction = createSharedFunction (() => fetch (' /api/me' ).then (r => r .json ()));
427+ export const chatSubscription = createSharedSubscription ((set , onError , onCompletion ) => {/* ... */ });
428+
429+ // Use anywhere in your app
430+ const [count, setCount] = useSharedState (counterState );
431+ const { state, trigger } = useSharedFunction (fetchUserFunction );
432+ const { state, trigger, unsubscribe } = useSharedSubscription (chatSubscription );
433+ ```
398434Useful for SSR hydration, event listeners, debugging, imperative workflows.
399435
400436``` ts
@@ -490,16 +526,25 @@ Currently no built-in Suspense wrappers; wrap `useSharedFunction` yourself if de
490526### ` useSharedState(key, initialValue, scopeName?) `
491527Returns ` [value, setValue] ` .
492528
529+ ### ` useSharedState(sharedStateCreated) `
530+ Returns ` [value, setValue] ` .
531+
493532### ` useSharedFunction(key, fn, scopeName?) `
494533Returns ` { state, trigger, forceTrigger, clear } ` .
495534
535+ ### ` useSharedFunction(sharedFunctionCreated) `
536+ Returns ` { state, trigger, forceTrigger, clear } ` .
537+
496538### ` useSharedSubscription(key, subscriber, scopeName?) `
497539Returns ` { state, trigger, unsubscribe } ` .
498540
541+ ### ` useSharedSubscription(sharedSubscriptionCreated) `
542+ Returns ` { state, trigger, unsubscribe } ` .
543+
499544### ` <SharedStatesProvider scopeName?> `
500545Wrap children; optional ` scopeName ` (string). If omitted a random unique one is generated.
501546
502- ### Static
547+ ### Static APIs
503548` sharedStatesApi ` , ` sharedFunctionsApi ` , ` sharedSubscriptionsApi ` (see earlier table).
504549
505550
0 commit comments