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 +57 -28
- package/dist/index.d.ts +2 -1
- package/dist/index.es.js +331 -314
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +4 -4
- package/dist/index.umd.js.map +1 -1
- package/dist/state-utils/createAutoCtx.d.ts +6 -1
- package/dist/state-utils/createRootCtx.d.ts +7 -3
- package/package.json +1 -1
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
|
|
23
|
-
3. **
|
|
24
|
-
4. **
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
127
|
-
const {
|
|
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
|
|
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
|
-
`
|
|
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 {
|
|
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
|
|
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 =
|
|
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 {
|
|
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
|
|
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 `
|
|
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 `
|
|
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';
|