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