react-state-custom 1.0.31 → 1.0.33
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 +102 -417
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +476 -420
- 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 +10 -0
- package/dist/state-utils/createRootCtx.d.ts +7 -3
- package/dist/state-utils/ctx.d.ts +1 -0
- package/dist/state-utils/utils.d.ts +8 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# React State Custom
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**The "It's Just a Hook" State Manager for React.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Turn any React hook into a global store. Zero boilerplate. Full type safety. Automatic lifecycle management.
|
|
6
6
|
|
|
7
7
|
[](https://vothanhdat.github.io/react-state-custom/)
|
|
8
8
|
[](https://www.npmjs.com/package/react-state-custom)
|
|
@@ -14,488 +14,178 @@ npm install react-state-custom
|
|
|
14
14
|
|
|
15
15
|
🎮 **[Try the Live Demo →](https://vothanhdat.github.io/react-state-custom/)**
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
---
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
## ⚡ The 30-Second Pitch
|
|
20
20
|
|
|
21
|
-
|
|
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.
|
|
21
|
+
Stop writing reducers, actions, and manual providers. If you can write a React hook, you've already written your store.
|
|
26
22
|
|
|
27
23
|
```tsx
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
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
|
-
|
|
61
|
-
## Why React State Custom?
|
|
62
|
-
|
|
63
|
-
**Zero Boilerplate** • **Type-Safe** • **Selective Re-renders** • **Hook-Based** • **~10KB Bundle**
|
|
64
|
-
|
|
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.
|
|
66
|
-
|
|
67
|
-
### When `useState` + `useEffect` Fall Short
|
|
68
|
-
|
|
69
|
-
Even though React hooks are flexible, they start to hurt once state crosses component boundaries:
|
|
70
|
-
|
|
71
|
-
- **Prop drilling & manual providers** – every time state needs to be shared, you create a context, memoize values, and remember to wrap trees.
|
|
72
|
-
- **Coarse-grained re-renders** – updating one property forces every subscriber of that context to render, even if they don't consume the changed field.
|
|
73
|
-
- **Lifecycle bookkeeping** – you manually manage instance lifetimes, clean up effects, and guard against components mounting before providers.
|
|
74
|
-
- **Zero visibility** – there's no built-in way to inspect shared state, throttle noisy updates, or keep debugging breadcrumbs.
|
|
75
|
-
|
|
76
|
-
React State Custom keeps your favorite hooks but layers on automatic context lifecycles, selective subscriptions, and built-in tooling so you can stay productive as your app grows.
|
|
77
|
-
|
|
78
|
-
## ⚡ Quick Example
|
|
79
|
-
|
|
80
|
-
### Without React State Custom (manual context plumbing)
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
const CounterContext = createContext<{
|
|
84
|
-
count: number;
|
|
85
|
-
increment: () => void;
|
|
86
|
-
decrement: () => void;
|
|
87
|
-
} | null>(null);
|
|
88
|
-
|
|
89
|
-
function CounterProvider({ children }: { children: React.ReactNode }) {
|
|
90
|
-
const [count, setCount] = useState(0);
|
|
91
|
-
const value = useMemo(
|
|
92
|
-
() => ({
|
|
93
|
-
count,
|
|
94
|
-
increment: () => setCount(c => c + 1),
|
|
95
|
-
decrement: () => setCount(c => c - 1),
|
|
96
|
-
}),
|
|
97
|
-
[count]
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
return <CounterContext.Provider value={value}>{children}</CounterContext.Provider>;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function useCounter() {
|
|
104
|
-
const ctx = useContext(CounterContext);
|
|
105
|
-
if (!ctx) throw new Error('CounterProvider missing');
|
|
106
|
-
return ctx;
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Every consumer re-renders whenever anything in `value` changes, you have to remember to wrap parts of the tree with `CounterProvider`, and tearing this pattern down for parameterized instances gets messy fast.
|
|
111
|
-
|
|
112
|
-
### With React State Custom (hook-first store)
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
import { createRootCtx, createAutoCtx, useQuickSubscribe, AutoRootCtx } from 'react-state-custom';
|
|
116
|
-
|
|
117
|
-
// 1. Write your state logic using familiar React hooks
|
|
118
|
-
function useCounterState() {
|
|
119
|
-
const [count, setCount] = useState(0);
|
|
120
|
-
const increment = () => setCount(c => c + 1);
|
|
121
|
-
const decrement = () => setCount(c => c - 1);
|
|
122
|
-
|
|
123
|
-
return { count, increment, decrement };
|
|
24
|
+
// 1. Write a standard hook (your store logic)
|
|
25
|
+
const useCountState = ({ initial = 0 }) => {
|
|
26
|
+
const [count, setCount] = useState(initial)
|
|
27
|
+
const increment = () => setCount(c => c + 1)
|
|
28
|
+
return { count, increment }
|
|
124
29
|
}
|
|
125
30
|
|
|
126
|
-
// 2. Create
|
|
127
|
-
const {
|
|
31
|
+
// 2. Create a store
|
|
32
|
+
export const { useStore } = createStore('counter', useCountState)
|
|
128
33
|
|
|
129
|
-
// 3.
|
|
34
|
+
// 3. Setup (mount once at root) & Use anywhere
|
|
130
35
|
function App() {
|
|
131
36
|
return (
|
|
132
37
|
<>
|
|
133
|
-
<AutoRootCtx />
|
|
38
|
+
<AutoRootCtx /> {/* 👈 The magic that manages your stores */}
|
|
134
39
|
<Counter />
|
|
135
40
|
</>
|
|
136
|
-
)
|
|
41
|
+
)
|
|
137
42
|
}
|
|
138
43
|
|
|
139
|
-
// 4. Use anywhere in your app
|
|
140
44
|
function Counter() {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<div>
|
|
146
|
-
<h1>{count}</h1>
|
|
147
|
-
<button onClick={increment}>+</button>
|
|
148
|
-
<button onClick={decrement}>-</button>
|
|
149
|
-
</div>
|
|
150
|
-
);
|
|
45
|
+
const { count, increment } = useStore({ initial: 10 })
|
|
46
|
+
return <button onClick={increment}>{count}</button>
|
|
151
47
|
}
|
|
152
48
|
```
|
|
153
49
|
|
|
154
|
-
|
|
50
|
+
**That's it.** No `Provider` wrapping per store. No complex setup. Just hooks.
|
|
155
51
|
|
|
156
|
-
|
|
52
|
+
---
|
|
157
53
|
|
|
158
|
-
|
|
54
|
+
## 🚀 Why React State Custom?
|
|
159
55
|
|
|
160
|
-
|
|
56
|
+
Most state libraries force you to learn a new way to write logic (reducers, atoms, proxies). **React State Custom** lets you use the React skills you already have.
|
|
161
57
|
|
|
162
|
-
|
|
163
|
-
|
|
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.
|
|
58
|
+
### 💎 Zero Boilerplate
|
|
59
|
+
Define state with `useState`, `useEffect`, `useMemo`. No new syntax to learn.
|
|
168
60
|
|
|
169
|
-
|
|
61
|
+
### 🎯 Selective Re-renders
|
|
62
|
+
Components only re-render when the specific data they use changes. Performance is built-in.
|
|
170
63
|
|
|
171
|
-
|
|
64
|
+
### 🔄 Automatic Lifecycle
|
|
65
|
+
Stores are created when needed and destroyed when unused. No more manual cleanup or memory leaks.
|
|
172
66
|
|
|
173
|
-
###
|
|
174
|
-
|
|
175
|
-
const ctx = useDataContext<MyState>('my-state');
|
|
176
|
-
```
|
|
67
|
+
### 🛡️ TypeScript First
|
|
68
|
+
Full type inference out of the box. Your IDE knows exactly what's in your store.
|
|
177
69
|
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
useDataSource(ctx, 'count', count);
|
|
181
|
-
```
|
|
70
|
+
---
|
|
182
71
|
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
const count = useDataSubscribe(ctx, 'count');
|
|
186
|
-
const { count, name } = useDataSubscribeMultiple(ctx, 'count', 'name');
|
|
187
|
-
```
|
|
72
|
+
## 🛠️ Quick Start
|
|
188
73
|
|
|
189
|
-
###
|
|
190
|
-
|
|
191
|
-
const { Root, useCtxState } = createRootCtx('my-state', useMyState);
|
|
192
|
-
```
|
|
74
|
+
### 1. Define Your State
|
|
75
|
+
Write a hook that returns the data and actions you want to share.
|
|
193
76
|
|
|
194
|
-
### 5. Auto context – mount roots for you
|
|
195
77
|
```typescript
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
## 🎯 Key Features
|
|
200
|
-
|
|
201
|
-
### 1. **Just React Hooks**
|
|
202
|
-
Use `useState`, `useEffect`, `useMemo`, and any other React hooks you already know. No new concepts to learn.
|
|
78
|
+
// features/userState.ts
|
|
79
|
+
import { useState, useEffect } from 'react'
|
|
203
80
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const [user, setUser] = useState(null);
|
|
207
|
-
const [loading, setLoading] = useState(true);
|
|
81
|
+
export const useUserState = ({ userId }: { userId: string }) => {
|
|
82
|
+
const [user, setUser] = useState(null)
|
|
208
83
|
|
|
209
84
|
useEffect(() => {
|
|
210
|
-
fetchUser(userId).then(setUser)
|
|
211
|
-
}, [userId])
|
|
212
|
-
|
|
213
|
-
return { user,
|
|
85
|
+
fetchUser(userId).then(setUser)
|
|
86
|
+
}, [userId])
|
|
87
|
+
|
|
88
|
+
return { user, isLoading: !user }
|
|
214
89
|
}
|
|
215
90
|
```
|
|
216
91
|
|
|
217
|
-
### 2.
|
|
218
|
-
|
|
92
|
+
### 2. Create the Store
|
|
93
|
+
Use `createStore` to generate a hook for your components.
|
|
219
94
|
|
|
220
95
|
```typescript
|
|
221
|
-
|
|
222
|
-
|
|
96
|
+
import { createStore } from 'react-state-custom'
|
|
97
|
+
import { useUserState } from './features/userState'
|
|
223
98
|
|
|
224
|
-
|
|
225
|
-
const { user, loading } = useDataSubscribeMultiple(ctx, 'user', 'loading');
|
|
99
|
+
export const { useStore: useUserStore } = createStore('user', useUserState)
|
|
226
100
|
```
|
|
227
101
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
### 3. **Automatic Context Management**
|
|
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.
|
|
232
|
-
|
|
233
|
-
### 4. **TypeScript First**
|
|
234
|
-
Full type inference and type safety throughout. Your IDE knows exactly what's in your state.
|
|
235
|
-
|
|
236
|
-
### 5. **Tiny Bundle Size**
|
|
237
|
-
~10KB gzipped. No dependencies except React.
|
|
238
|
-
|
|
239
|
-
## 🆚 Comparison with Hooks, Redux & Zustand
|
|
240
|
-
|
|
241
|
-
| Feature | React State Custom | Plain Hooks (Context) | Redux | Zustand |
|
|
242
|
-
|---------|-------------------|-----------------------|-------|---------|
|
|
243
|
-
| **Bundle Size** | ~10KB | 0KB (just React) | ~50KB (with toolkit) | ~1KB |
|
|
244
|
-
| **Learning Curve** | ✅ Minimal (just hooks) | ⚠️ Familiar APIs, but patterns are DIY | ❌ High (actions, reducers, middleware) | ✅ Low |
|
|
245
|
-
| **Boilerplate** | ✅ None | ❌ Manual providers + prop drilling | ❌ Heavy | ✅ Minimal |
|
|
246
|
-
| **Type Safety** | ✅ Full inference | ⚠️ Custom per-context typing | ⚠️ Requires setup | ✅ Good |
|
|
247
|
-
| **Selective Re-renders** | ✅ Built-in | ❌ Context update = every consumer renders | ⚠️ Requires selectors | ✅ Built-in |
|
|
248
|
-
| **DevTools** | ✅ Built-in UI | ❌ None | ✅ Redux DevTools | ✅ DevTools support |
|
|
249
|
-
| **Async Support** | ✅ Native (hooks) | ✅ Native (hooks) | ⚠️ Requires middleware | ✅ Native |
|
|
250
|
-
| **Context Composition** | ✅ Automatic | ❌ Manual provider trees | ❌ Manual | ⚠️ Manual store combination |
|
|
251
|
-
|
|
252
|
-
### When to Use React State Custom
|
|
253
|
-
|
|
254
|
-
✅ **Choose React State Custom if you:**
|
|
255
|
-
- Want to use React hooks for state management without learning new patterns
|
|
256
|
-
- Need fine-grained control over component re-renders
|
|
257
|
-
- Prefer minimal boilerplate and configuration
|
|
258
|
-
- Want automatic context lifecycle management
|
|
259
|
-
- Need multiple independent state contexts that don't interfere
|
|
260
|
-
|
|
261
|
-
❌ **Consider Redux if you:**
|
|
262
|
-
- Need powerful time-travel debugging (Redux DevTools)
|
|
263
|
-
- Have a very large team that benefits from strict architectural patterns
|
|
264
|
-
- Already have significant Redux investment
|
|
265
|
-
|
|
266
|
-
❌ **Consider Zustand if you:**
|
|
267
|
-
- Want the absolute smallest bundle size
|
|
268
|
-
- Need a simple global store without context isolation
|
|
269
|
-
- Don't need automatic context lifecycle management
|
|
270
|
-
|
|
271
|
-
## 🔥 Real-World Example: User Authentication
|
|
272
|
-
|
|
273
|
-
```typescript
|
|
274
|
-
// authState.ts
|
|
275
|
-
function useAuthState() {
|
|
276
|
-
const [user, setUser] = useState<User | null>(null);
|
|
277
|
-
const [loading, setLoading] = useState(true);
|
|
278
|
-
|
|
279
|
-
useEffect(() => {
|
|
280
|
-
// Check authentication on mount
|
|
281
|
-
checkAuth().then(setUser).finally(() => setLoading(false));
|
|
282
|
-
}, []);
|
|
283
|
-
|
|
284
|
-
const login = async (email: string, password: string) => {
|
|
285
|
-
setLoading(true);
|
|
286
|
-
try {
|
|
287
|
-
const user = await authService.login(email, password);
|
|
288
|
-
setUser(user);
|
|
289
|
-
} finally {
|
|
290
|
-
setLoading(false);
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
const logout = async () => {
|
|
295
|
-
await authService.logout();
|
|
296
|
-
setUser(null);
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
return { user, loading, login, logout };
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export const { useCtxState: useAuthState } = createAutoCtx(
|
|
303
|
-
createRootCtx('auth', useAuthState)
|
|
304
|
-
);
|
|
102
|
+
### 3. Mount the Root (Once)
|
|
103
|
+
Add `<AutoRootCtx />` to your app's root. This component manages all your stores automatically.
|
|
305
104
|
|
|
105
|
+
```tsx
|
|
306
106
|
// App.tsx
|
|
307
|
-
|
|
107
|
+
import { AutoRootCtx } from 'react-state-custom'
|
|
108
|
+
|
|
109
|
+
export default function App() {
|
|
308
110
|
return (
|
|
309
111
|
<>
|
|
310
112
|
<AutoRootCtx />
|
|
311
|
-
<
|
|
312
|
-
<Header />
|
|
313
|
-
<Routes />
|
|
314
|
-
</Router>
|
|
113
|
+
<YourAppContent />
|
|
315
114
|
</>
|
|
316
|
-
)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Header.tsx - Only re-renders when user changes
|
|
320
|
-
function Header() {
|
|
321
|
-
const ctx = useAuthState();
|
|
322
|
-
const { user, logout } = useQuickSubscribe(ctx);
|
|
323
|
-
|
|
324
|
-
return (
|
|
325
|
-
<header>
|
|
326
|
-
{user ? (
|
|
327
|
-
<>
|
|
328
|
-
<span>Welcome, {user.name}</span>
|
|
329
|
-
<button onClick={logout}>Logout</button>
|
|
330
|
-
</>
|
|
331
|
-
) : (
|
|
332
|
-
<Link to="/login">Login</Link>
|
|
333
|
-
)}
|
|
334
|
-
</header>
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// ProtectedRoute.tsx - Only re-renders when loading or user changes
|
|
339
|
-
function ProtectedRoute({ children }) {
|
|
340
|
-
const ctx = useAuthState();
|
|
341
|
-
const { user, loading } = useDataSubscribeMultiple(ctx, 'user', 'loading');
|
|
342
|
-
|
|
343
|
-
if (loading) return <Spinner />;
|
|
344
|
-
if (!user) return <Navigate to="/login" />;
|
|
345
|
-
|
|
346
|
-
return children;
|
|
115
|
+
)
|
|
347
116
|
}
|
|
348
117
|
```
|
|
349
118
|
|
|
350
|
-
|
|
351
|
-
```typescript
|
|
352
|
-
// Redux requires: action types, action creators, reducers, thunks/sagas
|
|
353
|
-
// React State Custom: just write a hook! ✨
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
## 🚀 Advanced Features
|
|
119
|
+
### 🎭 Isolated State
|
|
357
120
|
|
|
358
|
-
|
|
121
|
+
Need to run multiple independent instances of your application or isolate features? Use `StateScopeProvider`.
|
|
359
122
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
import { DevToolContainer } from 'react-state-custom';
|
|
365
|
-
import 'react-state-custom/dist/react-state-custom.css';
|
|
123
|
+
```tsx
|
|
124
|
+
import { AutoRootCtx, StateScopeProvider } from 'react-state-custom'
|
|
366
125
|
|
|
367
126
|
function App() {
|
|
368
127
|
return (
|
|
369
128
|
<>
|
|
370
|
-
<AutoRootCtx />
|
|
371
|
-
<
|
|
372
|
-
|
|
129
|
+
<AutoRootCtx /> {/* Global Scope */}
|
|
130
|
+
<MainApp />
|
|
131
|
+
|
|
132
|
+
<StateScopeProvider>
|
|
133
|
+
{/* Isolated Scope - Stores here are independent of Global Scope */}
|
|
134
|
+
<IsolatedFeature />
|
|
135
|
+
</StateScopeProvider>
|
|
373
136
|
</>
|
|
374
|
-
)
|
|
137
|
+
)
|
|
375
138
|
}
|
|
376
139
|
```
|
|
377
140
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
**Custom data viewer with rich object visualization:**
|
|
381
|
-
```typescript
|
|
382
|
-
import { DataViewComponent } from 'react-state-custom';
|
|
383
|
-
import { ObjectView } from 'react-obj-view';
|
|
384
|
-
import 'react-obj-view/dist/react-obj-view.css'; // Required for ObjectView styling
|
|
385
|
-
|
|
386
|
-
const CustomDataView: DataViewComponent = ({ name, value }) => {
|
|
387
|
-
return <ObjectView name={name} value={value} expandLevel={2} />;
|
|
388
|
-
};
|
|
141
|
+
Stores used inside `StateScopeProvider` will be completely isolated from the parent or global scope, even if they share the same store definition.
|
|
389
142
|
|
|
390
|
-
|
|
391
|
-
```
|
|
143
|
+
---
|
|
392
144
|
|
|
393
|
-
|
|
145
|
+
## 🆚 Comparison
|
|
394
146
|
|
|
395
|
-
|
|
396
|
-
|
|
147
|
+
| Feature | React State Custom | Redux | Context API | Zustand |
|
|
148
|
+
|:---|:---:|:---:|:---:|:---:|
|
|
149
|
+
| **Paradigm** | Just Hooks 🪝 | Actions/Reducers | Providers | Store Object |
|
|
150
|
+
| **Boilerplate** | 🟢 None | 🔴 High | 🟡 Medium | 🟢 Low |
|
|
151
|
+
| **Auto Lifecycle** | ✅ Yes | ❌ No | ❌ No | ❌ No |
|
|
152
|
+
| **Selective Renders** | ✅ Automatic | ⚠️ Selectors | ❌ Manual | ✅ Selectors |
|
|
153
|
+
| **Learning Curve** | 🟢 Low | 🔴 High | 🟡 Medium | 🟢 Low |
|
|
397
154
|
|
|
398
|
-
|
|
399
|
-
function useUserState({ userId }: { userId: string }) {
|
|
400
|
-
// State logic here
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const { useCtxState: useUserCtxState } = createAutoCtx(
|
|
404
|
-
createRootCtx('user', useUserState)
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
// Different instances for different users
|
|
408
|
-
function UserProfile({ userId }) {
|
|
409
|
-
const ctx = useUserCtxState({ userId }); // Automatic instance per userId
|
|
410
|
-
const { user } = useQuickSubscribe(ctx);
|
|
411
|
-
return <div>{user?.name}</div>;
|
|
412
|
-
}
|
|
413
|
-
```
|
|
155
|
+
---
|
|
414
156
|
|
|
415
|
-
|
|
157
|
+
## 🧩 Advanced Features
|
|
416
158
|
|
|
417
|
-
|
|
159
|
+
### 🔌 Developer Tools
|
|
160
|
+
Inspect your state in real-time with the built-in DevTools.
|
|
418
161
|
|
|
419
|
-
|
|
420
|
-
|
|
162
|
+
```tsx
|
|
163
|
+
import { DevToolContainer } from 'react-state-custom'
|
|
164
|
+
import 'react-state-custom/dist/react-state-custom.css'
|
|
421
165
|
|
|
422
|
-
|
|
423
|
-
// Re-render at most once per 300ms
|
|
424
|
-
const searchQuery = useDataSubscribe(ctx, 'searchQuery', 300);
|
|
166
|
+
<DevToolContainer />
|
|
425
167
|
```
|
|
426
168
|
|
|
427
|
-
###
|
|
428
|
-
|
|
169
|
+
### 🆔 Parameterized Stores
|
|
170
|
+
Create multiple independent instances of the same store by passing different parameters.
|
|
429
171
|
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
(user) => ({
|
|
435
|
-
fullName: `${user?.firstName} ${user?.lastName}`,
|
|
436
|
-
isAdmin: user?.role === 'admin'
|
|
437
|
-
})
|
|
438
|
-
);
|
|
172
|
+
```tsx
|
|
173
|
+
// Creates a unique store for each ID
|
|
174
|
+
const { count } = useStore({ id: 'counter-1' })
|
|
175
|
+
const { count } = useStore({ id: 'counter-2' })
|
|
439
176
|
```
|
|
440
177
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
Explore interactive examples in the **[Live Demo](https://vothanhdat.github.io/react-state-custom/)**:
|
|
444
|
-
|
|
445
|
-
- **Counter** - Basic state management with increment, decrement, and reset
|
|
446
|
-
- **Todo List** - Multiple independent lists with scoped contexts
|
|
447
|
-
- **Form Validation** - Real-time validation with error handling
|
|
448
|
-
- **Timer** - Side effects and cleanup with millisecond precision
|
|
449
|
-
- **Shopping Cart** - Complex state with derived values (total, itemCount)
|
|
450
|
-
|
|
451
|
-
Each example includes live code editing with syntax highlighting, powered by Sandpack!
|
|
452
|
-
|
|
453
|
-
## 📖 Documentation
|
|
454
|
-
|
|
455
|
-
For complete API documentation, examples, and advanced patterns, see:
|
|
456
|
-
- **[API_DOCUMENTATION.md](./API_DOCUMENTATION.md)** - Complete API reference
|
|
457
|
-
- **[Live Demo](https://vothanhdat.github.io/react-state-custom/)** - Interactive examples
|
|
458
|
-
|
|
459
|
-
## 🛠️ Development
|
|
460
|
-
|
|
461
|
-
```bash
|
|
462
|
-
# Install dependencies
|
|
463
|
-
yarn install
|
|
464
|
-
|
|
465
|
-
# Run development UI with example selector
|
|
466
|
-
yarn dev
|
|
467
|
-
|
|
468
|
-
# Run interactive playground with live code editing
|
|
469
|
-
yarn dev:playground
|
|
470
|
-
|
|
471
|
-
# Build library
|
|
472
|
-
yarn build
|
|
473
|
-
|
|
474
|
-
# Build demo site
|
|
475
|
-
yarn build:demo
|
|
178
|
+
### ⚡️ Derived State
|
|
179
|
+
Compose stores just like hooks.
|
|
476
180
|
|
|
477
|
-
|
|
478
|
-
|
|
181
|
+
```tsx
|
|
182
|
+
const useCartTotal = () => {
|
|
183
|
+
const { items } = useCartStore({})
|
|
184
|
+
return items.reduce((total, item) => total + item.price, 0)
|
|
185
|
+
}
|
|
479
186
|
```
|
|
480
187
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
**`yarn dev`** - Starts a clean development UI with an interactive example selector. Great for:
|
|
484
|
-
- Testing all examples in one place
|
|
485
|
-
- Quick switching between different examples
|
|
486
|
-
- Visual debugging with DevTool component
|
|
487
|
-
|
|
488
|
-
**`yarn dev:playground`** - Starts the Sandpack-powered playground with live code editing. Perfect for:
|
|
489
|
-
- Creating interactive demos
|
|
490
|
-
- Live code editing and experimentation
|
|
491
|
-
- Sharing editable examples
|
|
492
|
-
|
|
493
|
-
## 🎓 Learning Path
|
|
494
|
-
|
|
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.
|
|
188
|
+
---
|
|
499
189
|
|
|
500
190
|
## 📦 Installation
|
|
501
191
|
|
|
@@ -503,18 +193,13 @@ yarn preview
|
|
|
503
193
|
npm install react-state-custom
|
|
504
194
|
# or
|
|
505
195
|
yarn add react-state-custom
|
|
506
|
-
# or
|
|
507
|
-
pnpm add react-state-custom
|
|
508
196
|
```
|
|
509
197
|
|
|
510
|
-
##
|
|
198
|
+
## 📖 Documentation
|
|
511
199
|
|
|
512
|
-
|
|
200
|
+
- **[API Reference](./API_DOCUMENTATION.md)** - Full API documentation.
|
|
201
|
+
- **[Live Demo](https://vothanhdat.github.io/react-state-custom/)** - Interactive examples.
|
|
513
202
|
|
|
514
203
|
## 📄 License
|
|
515
204
|
|
|
516
|
-
MIT
|
|
517
|
-
|
|
518
|
-
---
|
|
519
|
-
|
|
520
|
-
**Made with ❤️ for developers who love React hooks**
|
|
205
|
+
MIT © Vo Thanh Dat
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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, StateScopeProvider } from './state-utils/createAutoCtx';
|
|
4
4
|
export { useArrayChangeId } from './state-utils/useArrayChangeId';
|
|
5
5
|
export { paramsToId, type ParamsToIdRecord, type ParamsToIdInput } from './state-utils/paramsToId';
|
|
6
6
|
export { useQuickSubscribe } from './state-utils/useQuickSubscribe';
|