react-state-custom 1.0.29 → 1.0.31

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Simple. Powerful. TypeScript-first.**
4
4
 
5
- A lightweight React state management library that combines the simplicity of React hooks with the power of event-driven subscriptions. No boilerplate, no complexity—just pure, performant state management.
5
+ A lightweight global React state management library that combines the simplicity of React hooks with the power of event-driven subscriptions. No boilerplate, no complexity—just pure, performant state management.
6
6
 
7
7
  [![Demo](https://img.shields.io/badge/Demo-Live-blue?style=flat-square)](https://vothanhdat.github.io/react-state-custom/)
8
8
  [![npm version](https://img.shields.io/npm/v/react-state-custom?style=flat-square)](https://www.npmjs.com/package/react-state-custom)
@@ -14,11 +14,55 @@ npm install react-state-custom
14
14
 
15
15
  🎮 **[Try the Live Demo →](https://vothanhdat.github.io/react-state-custom/)**
16
16
 
17
+ ## Quick Start (2 minutes)
18
+
19
+ If you already know how to write a component with `useState`, you're moments away from sharing that state everywhere.
20
+
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.
26
+
27
+ ```tsx
28
+ const useFeatureState = ({ featureId }: { featureId: string }) => {
29
+ const [value, setValue] = useState(0)
30
+ const double = useMemo(() => value * 2, [value])
31
+ return { value, double, increment: () => setValue(v => v + 1) }
32
+ }
33
+
34
+ const featureRoot = createRootCtx('feature', useFeatureState)
35
+ export const { useCtxState: useFeatureCtx } = createAutoCtx(featureRoot, 250)
36
+
37
+ function AppShell() {
38
+ return (
39
+ <>
40
+ <AutoRootCtx Wrapper={ErrorBoundary} debugging={import.meta.env.DEV} />
41
+ <Routes />
42
+ </>
43
+ )
44
+ }
45
+
46
+ function FeatureMeter({ featureId }: { featureId: string }) {
47
+ const ctx = useFeatureCtx({ featureId })
48
+ const { value, double, increment } = useQuickSubscribe(ctx)
49
+ return (
50
+ <section>
51
+ <strong>{value}</strong>
52
+ <em>{double}</em>
53
+ <button onClick={increment}>Add</button>
54
+ </section>
55
+ )
56
+ }
57
+ ```
58
+
59
+ That’s the entire workflow—no reducers, actions, or provider trees.
60
+
17
61
  ## Why React State Custom?
18
62
 
19
63
  **Zero Boilerplate** • **Type-Safe** • **Selective Re-renders** • **Hook-Based** • **~10KB Bundle**
20
64
 
21
- React State Custom lets you write state management code that feels natural—because it **is** just React hooks. Use the same hooks you already know (`useState`, `useEffect`, etc.) to create powerful, shared state without learning new paradigms.
65
+ React State Custom lets you write state management code that feels natural—because it **is** just React hooks. Use the same hooks you already know (`useState`, `useEffect`, etc.) to create powerful, shared global state without learning new paradigms.
22
66
 
23
67
  ### When `useState` + `useEffect` Fall Short
24
68
 
@@ -113,6 +157,45 @@ function Counter() {
113
157
 
114
158
  **That's it!** No reducers, no actions, no providers to wrap—just hooks.
115
159
 
160
+ ## Core Concepts in Plain English
161
+
162
+ - **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
+ - **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
+ - **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.
166
+ - **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
+ - **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
+
169
+ ## Core Building Blocks (copy & paste ready)
170
+
171
+ Familiarity beats theory, so here are the primitives you’ll reach for most often:
172
+
173
+ ### 1. Context – event-driven store
174
+ ```typescript
175
+ const ctx = useDataContext<MyState>('my-state');
176
+ ```
177
+
178
+ ### 2. Data source – publish values
179
+ ```typescript
180
+ useDataSource(ctx, 'count', count);
181
+ ```
182
+
183
+ ### 3. Subscribers – pick exact fields
184
+ ```typescript
185
+ const count = useDataSubscribe(ctx, 'count');
186
+ const { count, name } = useDataSubscribeMultiple(ctx, 'count', 'name');
187
+ ```
188
+
189
+ ### 4. Root context – run your hook once
190
+ ```typescript
191
+ const { Root, useCtxState } = createRootCtx('my-state', useMyState);
192
+ ```
193
+
194
+ ### 5. Auto context – mount roots for you
195
+ ```typescript
196
+ const { useCtxState } = createAutoCtx(rootContext);
197
+ ```
198
+
116
199
  ## 🎯 Key Features
117
200
 
118
201
  ### 1. **Just React Hooks**
@@ -142,6 +225,8 @@ const { user } = useDataSubscribeMultiple(ctx, 'user');
142
225
  const { user, loading } = useDataSubscribeMultiple(ctx, 'user', 'loading');
143
226
  ```
144
227
 
228
+ > ⚠️ `useQuickSubscribe` proxies are only readable during render. Destructure the properties you need immediately and avoid storing the proxy in refs, effects, or callbacks.
229
+
145
230
  ### 3. **Automatic Context Management**
146
231
  With `AutoRootCtx`, state contexts are automatically created and destroyed as needed. Mount it once near your application root, optionally providing a `Wrapper` (for error boundaries) or enabling `debugging` to render live state snapshots in the DOM—useful context when pairing with React DevTools. No manual provider management required.
147
232
 
@@ -268,36 +353,10 @@ function ProtectedRoute({ children }) {
268
353
  // React State Custom: just write a hook! ✨
269
354
  ```
270
355
 
271
- ## 📚 Core Concepts
272
-
273
- ### 1. **Context** - Event-driven state container
274
- ```typescript
275
- const ctx = useDataContext<MyState>('my-state');
276
- ```
277
-
278
- ### 2. **Data Source** - Publish values to context
279
- ```typescript
280
- useDataSource(ctx, 'count', count);
281
- ```
282
-
283
- ### 3. **Data Subscription** - Subscribe to specific values
284
- ```typescript
285
- const count = useDataSubscribe(ctx, 'count');
286
- const { count, name } = useDataSubscribeMultiple(ctx, 'count', 'name');
287
- ```
288
-
289
- ### 4. **Root Context** - Lifecycle-managed context
290
- ```typescript
291
- const { Root, useCtxState } = createRootCtx('my-state', useMyState);
292
- ```
293
-
294
- ### 5. **Auto Context** - Automatic instance management
295
- ```typescript
296
- const { useCtxState } = createAutoCtx(rootContext);
297
- ```
298
-
299
356
  ## 🚀 Advanced Features
300
357
 
358
+ Once you have a store running, layer in these power-ups as needed.
359
+
301
360
  ### Developer Tools
302
361
  Visual debugging component to inspect all your context data in real-time:
303
362
 
@@ -331,6 +390,8 @@ const CustomDataView: DataViewComponent = ({ name, value }) => {
331
390
  <DevToolContainer Component={CustomDataView} />
332
391
  ```
333
392
 
393
+ Pass `children` to `DevToolContainer` to customize the floating toggle button label (for example `<DevToolContainer>State Inspector</DevToolContainer>`), and import `react-state-custom/dist/react-state-custom.css` once to pick up the overlay styles.
394
+
334
395
  ### Parameterized Contexts
335
396
  Create multiple instances of the same state with different parameters:
336
397
 
@@ -353,6 +414,8 @@ function UserProfile({ userId }) {
353
414
 
354
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.
355
416
 
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.
418
+
356
419
  ### Debounced Subscriptions
357
420
  Optimize performance for frequently changing values:
358
421
 
@@ -429,10 +492,10 @@ yarn preview
429
492
 
430
493
  ## 🎓 Learning Path
431
494
 
432
- 1. **Start Simple** - Use `createRootCtx` + `createAutoCtx` for basic state
433
- 2. **Add Subscriptions** - Use `useDataSubscribeMultiple` for selective re-renders
434
- 3. **Optimize** - Add debouncing and transformations as needed
435
- 4. **Scale** - Create parameterized contexts for dynamic instances
495
+ 1. **Follow the Quick Start** build one shared store end-to-end.
496
+ 2. **Layer on subscriptions** swap `useQuickSubscribe` for the more specific `useDataSubscribe*` hooks where it makes sense.
497
+ 3. **Optimize when needed** introduce debounced/transform subscriptions and `createAutoCtx` grace periods to smooth noisy stores.
498
+ 4. **Scale up** add parameterized contexts (one store per ID) and wire the DevTool overlay for visibility.
436
499
 
437
500
  ## 📦 Installation
438
501
 
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { Context, getContext, useDataContext, useDataSource, useDataSourceMultip
2
2
  export { createRootCtx } from './state-utils/createRootCtx';
3
3
  export { AutoRootCtx, createAutoCtx } 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';