react-state-custom 1.0.30 → 1.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,10 +19,9 @@ npm install react-state-custom
19
19
  If you already know how to write a component with `useState`, you're moments away from sharing that state everywhere.
20
20
 
21
21
  1. **Write a plain hook** – encapuslate data fetching, derived values, and actions inside a normal React hook.
22
- 2. **Create a root context** – `createRootCtx('feature', useFeatureState)` publishes the hook output into a context namespace.
23
- 3. **Let AutoRoot manage lifecycles** – `createAutoCtx(rootCtx, 150)` registers the store with `<AutoRootCtx />` and (optionally) keeps it alive for a short grace period after the last subscriber unmounts.
24
- 4. **Mount `<AutoRootCtx />` once** – drop it near the top of your tree (wrap it with your own `ErrorBoundary` if desired).
25
- 5. **Consume anywhere** – call the generated `useCtxState` hook and destructure data via `useQuickSubscribe` or any `useDataSubscribe*` helper.
22
+ 2. **Create a store** – `createStore('feature', useFeatureState)` creates a shared store and returns a `useStore` hook.
23
+ 3. **Mount `<AutoRootCtx />` once** – drop it near the top of your tree (wrap it with your own `ErrorBoundary` if desired).
24
+ 4. **Consume anywhere** – call the generated `useStore` hook to access data and actions.
26
25
 
27
26
  ```tsx
28
27
  const useFeatureState = ({ featureId }: { featureId: string }) => {
@@ -31,8 +30,7 @@ const useFeatureState = ({ featureId }: { featureId: string }) => {
31
30
  return { value, double, increment: () => setValue(v => v + 1) }
32
31
  }
33
32
 
34
- const featureRoot = createRootCtx('feature', useFeatureState)
35
- export const { useCtxState: useFeatureCtx } = createAutoCtx(featureRoot, 250)
33
+ export const { useStore: useFeatureStore } = createStore('feature', useFeatureState)
36
34
 
37
35
  function AppShell() {
38
36
  return (
@@ -44,8 +42,7 @@ function AppShell() {
44
42
  }
45
43
 
46
44
  function FeatureMeter({ featureId }: { featureId: string }) {
47
- const ctx = useFeatureCtx({ featureId })
48
- const { value, double, increment } = useQuickSubscribe(ctx)
45
+ const { value, double, increment } = useFeatureStore({ featureId })
49
46
  return (
50
47
  <section>
51
48
  <strong>{value}</strong>
@@ -111,8 +108,10 @@ Every consumer re-renders whenever anything in `value` changes, you have to reme
111
108
 
112
109
  ### With React State Custom (hook-first store)
113
110
 
111
+ ### With React State Custom (hook-first store)
112
+
114
113
  ```typescript
115
- import { createRootCtx, createAutoCtx, useQuickSubscribe, AutoRootCtx } from 'react-state-custom';
114
+ import { createStore, AutoRootCtx } from 'react-state-custom';
116
115
 
117
116
  // 1. Write your state logic using familiar React hooks
118
117
  function useCounterState() {
@@ -123,8 +122,8 @@ function useCounterState() {
123
122
  return { count, increment, decrement };
124
123
  }
125
124
 
126
- // 2. Create shared context (one line!)
127
- const { useCtxState } = createAutoCtx(createRootCtx('counter', useCounterState));
125
+ // 2. Create shared store (one line!)
126
+ const { useStore } = createStore('counter', useCounterState);
128
127
 
129
128
  // 3. Add AutoRootCtx to your app root (mount it once near the top of your tree)
130
129
  function App() {
@@ -138,8 +137,7 @@ function App() {
138
137
 
139
138
  // 4. Use anywhere in your app
140
139
  function Counter() {
141
- const ctx = useCtxState();
142
- const { count, increment, decrement } = useQuickSubscribe(ctx);
140
+ const { count, increment, decrement } = useStore({});
143
141
 
144
142
  return (
145
143
  <div>
@@ -153,7 +151,7 @@ function Counter() {
153
151
 
154
152
  > ℹ️ `AutoRootCtx` accepts optional `Wrapper` and `debugging` props. Pass an ErrorBoundary-like component through `Wrapper` to isolate failures, or set `debugging` to `true` to render raw state snapshots in the DOM (handy alongside React DevTools when tracking updates).
155
153
 
156
- `useQuickSubscribe` keeps `Counter` focused on `count`, so even if this context grows with more fields later, the component only re-renders when `count` changes.
154
+ `useStore` keeps `Counter` focused on `count`, so even if this context grows with more fields later, the component only re-renders when `count` changes.
157
155
 
158
156
  **That's it!** No reducers, no actions, no providers to wrap—just hooks.
159
157
 
@@ -162,7 +160,8 @@ function Counter() {
162
160
  - **Contexts on demand** – `Context` extends `EventTarget`, so every state update is just an event dispatch. `getContext` memoizes instances per name and `useDataContext` automatically bumps a counter so unused contexts self-evict shortly after the last consumer unmounts.
163
161
  - **Publishers** – `useDataSource` and `useDataSourceMultiple` publish inside effects to keep renders pure. A registry guards against duplicate publishers fighting over the same key so you get actionable errors instead of stale data.
164
162
  - **Subscribers** – `useDataSubscribe*` hooks cover single, multiple, debounced, and transformed reads. `useQuickSubscribe` proxies the backing data object so each component subscribes only to the properties it touches.
165
- - **Root factories** – `createRootCtx` runs your headless hook exactly once per parameter set, publishes every returned key, and throws if two roots try to mount with the same resolved name. Parameters are serialized via `paramsToId`, so stick to primitive props (string/number/boolean/bigint/null/undefined) to keep IDs deterministic.
163
+ - **Root factories** – `createRootCtx` runs your headless hook exactly once per parameter set, publishes every returned key, and throws if two roots try to mount with the same resolved name. Your hook receives `(props, preState)` so it can rehydrate from the last published values when a root remounts. Parameters are serialized via `paramsToId`, so stick to primitive props (string/number/boolean/bigint/null/undefined) to keep IDs deterministic.
164
+ - **Composable Stores** – Because stores are just hooks, you can subscribe to one store *inside* the logic of another. This enables powerful reactive chains where a derived store automatically updates whenever its upstream dependencies change.
166
165
  - **Auto orchestration** – Mount `<AutoRootCtx />` once and wire each root through `createAutoCtx`. The auto root listens for subscription requests, mounts/destroys the corresponding root on demand, and optionally keeps them alive for a configurable `timeToClean` window to smooth thrashing.
167
166
  - **Dev tooling** – `DevToolContainer` watches the memoized context cache, flashes updates in place, and lets you plug in custom renderers so you can diff state right beside your UI.
168
167
 
@@ -196,6 +195,16 @@ const { Root, useCtxState } = createRootCtx('my-state', useMyState);
196
195
  const { useCtxState } = createAutoCtx(rootContext);
197
196
  ```
198
197
 
198
+ ### 6. Store factory – all in one
199
+ ```typescript
200
+ const { useStore } = createStore('my-state', useMyState);
201
+ ```
202
+
203
+ ### 6. Store factory – all in one
204
+ ```typescript
205
+ const { useStore } = createStore('my-state', useMyState);
206
+ ```
207
+
199
208
  ## 🎯 Key Features
200
209
 
201
210
  ### 1. **Just React Hooks**
@@ -299,9 +308,7 @@ function useAuthState() {
299
308
  return { user, loading, login, logout };
300
309
  }
301
310
 
302
- export const { useCtxState: useAuthState } = createAutoCtx(
303
- createRootCtx('auth', useAuthState)
304
- );
311
+ export const { useStore: useAuthStore } = createStore('auth', useAuthState);
305
312
 
306
313
  // App.tsx
307
314
  function App() {
@@ -318,8 +325,7 @@ function App() {
318
325
 
319
326
  // Header.tsx - Only re-renders when user changes
320
327
  function Header() {
321
- const ctx = useAuthState();
322
- const { user, logout } = useQuickSubscribe(ctx);
328
+ const { user, logout } = useAuthStore({});
323
329
 
324
330
  return (
325
331
  <header>
@@ -337,7 +343,7 @@ function Header() {
337
343
 
338
344
  // ProtectedRoute.tsx - Only re-renders when loading or user changes
339
345
  function ProtectedRoute({ children }) {
340
- const ctx = useAuthState();
346
+ const ctx = useAuthStore.useCtxState({});
341
347
  const { user, loading } = useDataSubscribeMultiple(ctx, 'user', 'loading');
342
348
 
343
349
  if (loading) return <Spinner />;
@@ -400,21 +406,18 @@ function useUserState({ userId }: { userId: string }) {
400
406
  // State logic here
401
407
  }
402
408
 
403
- const { useCtxState: useUserCtxState } = createAutoCtx(
404
- createRootCtx('user', useUserState)
405
- );
409
+ const { useStore: useUserStore } = createStore('user', useUserState);
406
410
 
407
411
  // Different instances for different users
408
412
  function UserProfile({ userId }) {
409
- const ctx = useUserCtxState({ userId }); // Automatic instance per userId
410
- const { user } = useQuickSubscribe(ctx);
413
+ const { user } = useUserStore({ userId }); // Automatic instance per userId
411
414
  return <div>{user?.name}</div>;
412
415
  }
413
416
  ```
414
417
 
415
- > Need to avoid rapid mount/unmount churn? Pass a second argument to `createAutoCtx` (for example `createAutoCtx(rootCtx, 200)`) to keep instances alive for a few extra milliseconds before disposal.
418
+ > Need to avoid rapid mount/unmount churn? Pass a second argument to `createStore` (for example `createStore('user', useUserState, 200)`) to keep instances alive for a few extra milliseconds before disposal.
416
419
 
417
- > ⚠️ The props you pass to `createRootCtx`/`useCtxState` must be composed of primitive values (string, number, boolean, bigint, null, or undefined). Objects are rejected so context names stay deterministic—pass IDs instead of raw objects.
420
+ > ⚠️ The props you pass to `createStore`/`useStore` must be composed of primitive values (string, number, boolean, bigint, null, or undefined). Objects are rejected so context names stay deterministic—pass IDs instead of raw objects.
418
421
 
419
422
  ### Debounced Subscriptions
420
423
  Optimize performance for frequently changing values:
@@ -438,6 +441,32 @@ const userStats = useDataSubscribeWithTransform(
438
441
  );
439
442
  ```
440
443
 
444
+ ### Composing Stores (Derived State)
445
+ Since stores are just hooks, you can subscribe to one store *inside* another. This allows you to build reactive dependency chains where a downstream store automatically updates when an upstream store changes.
446
+
447
+ ```typescript
448
+ // 1. Upstream Store
449
+ const { useStore: useUserStore } = createStore('user', () => {
450
+ const [role, setRole] = useState('guest');
451
+ return { role, setRole };
452
+ });
453
+
454
+ // 2. Downstream Store (depends on User)
455
+ const useDashboardStore = () => {
456
+ // Subscribe to the upstream store
457
+ const { role } = useUserStore({});
458
+
459
+ // Derive state based on the upstream value
460
+ const permissions = useMemo(() => {
461
+ return role === 'admin' ? ['read', 'write', 'delete'] : ['read'];
462
+ }, [role]);
463
+
464
+ return { permissions };
465
+ };
466
+
467
+ const { useStore: useDashboardStore } = createStore('dashboard', useDashboardStore);
468
+ ```
469
+
441
470
  ## 🎮 Live Examples
442
471
 
443
472
  Explore interactive examples in the **[Live Demo](https://vothanhdat.github.io/react-state-custom/)**:
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export { Context, getContext, useDataContext, useDataSource, useDataSourceMultiple, useDataSubscribe, useDataSubscribeMultiple, useDataSubscribeMultipleWithDebounce, useDataSubscribeWithTransform } from './state-utils/ctx';
2
2
  export { createRootCtx } from './state-utils/createRootCtx';
3
- export { AutoRootCtx, createAutoCtx } from './state-utils/createAutoCtx';
3
+ export { AutoRootCtx, createAutoCtx, createStore } from './state-utils/createAutoCtx';
4
4
  export { useArrayChangeId } from './state-utils/useArrayChangeId';
5
+ export { paramsToId, type ParamsToIdRecord, type ParamsToIdInput } from './state-utils/paramsToId';
5
6
  export { useQuickSubscribe } from './state-utils/useQuickSubscribe';
6
7
  export { DevToolContainer } from './dev-tool/DevTool';
7
8
  export type { DataViewComponent } from './dev-tool/DataViewComponent';