react-state-custom 1.0.10 → 1.0.12
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/API_DOCUMENTATION.md +883 -0
- package/README.md +180 -40
- package/dist/index.d.ts +2 -2
- package/dist/index.es.js +403 -403
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +5 -4
- package/dist/index.umd.js.map +1 -0
- package/dist/state-utils/createAutoCtx.d.ts +47 -0
- package/dist/state-utils/createRootCtx.d.ts +37 -0
- package/dist/state-utils/ctx.d.ts +1 -1
- package/dist/state-utils/useArrayHash.d.ts +15 -0
- package/dist/state-utils/useObjectHash.d.ts +15 -1
- package/dist/state-utils/useQuickSubscribe.d.ts +17 -0
- package/package.json +5 -3
- package/src/index.ts +2 -2
- package/src/state-utils/createAutoCtx.tsx +51 -2
- package/src/state-utils/createRootCtx.tsx +37 -0
- package/src/state-utils/ctx.ts +11 -9
- package/src/state-utils/useArrayHash.ts +53 -0
- package/src/state-utils/useObjectHash.ts +24 -1
- package/src/state-utils/useQuickSubscribe.ts +60 -65
- package/vite.config.ts +2 -1
- package/dist/components/MyComponent.d.ts +0 -7
- package/dist/state-utils/useQuickSubscribeV2.d.ts +0 -2
- package/src/components/MyComponent.tsx +0 -17
- package/src/state-utils/useQuickSubscribeV2.ts +0 -93
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
# React State Custom - API Documentation
|
|
2
|
+
|
|
3
|
+
A powerful React library for managing shared state and context with TypeScript support, built with Vite.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Core Context System](#core-context-system)
|
|
8
|
+
- [Context Class](#context-class)
|
|
9
|
+
- [getContext](#getcontext)
|
|
10
|
+
- [useDataContext](#usedatacontext)
|
|
11
|
+
2. [Data Source Hooks](#data-source-hooks)
|
|
12
|
+
- [useDataSource](#usedatasource)
|
|
13
|
+
- [useDataSourceMultiple](#usedatasourcemultiple)
|
|
14
|
+
3. [Data Subscription Hooks](#data-subscription-hooks)
|
|
15
|
+
- [useDataSubscribe](#usedatasubscribe)
|
|
16
|
+
- [useDataSubscribeMultiple](#usedatasubscribemultiple)
|
|
17
|
+
- [useDataSubscribeMultipleWithDebounce](#usedatasubscribemultiplewithbounce)
|
|
18
|
+
- [useDataSubscribeWithTransform](#usedatasubscribewithtransform)
|
|
19
|
+
4. [Root Context Factory](#root-context-factory)
|
|
20
|
+
- [createRootCtx](#createrootctx)
|
|
21
|
+
5. [Auto Context System](#auto-context-system)
|
|
22
|
+
- [AutoRootCtx](#autorootctx)
|
|
23
|
+
- [createAutoCtx](#createautoctx)
|
|
24
|
+
6. [Utility Hooks](#utility-hooks)
|
|
25
|
+
- [useArrayHash](#usearrayhash)
|
|
26
|
+
- [useQuickSubscribe](#usequicksubscribe)
|
|
27
|
+
7. [Usage Patterns](#usage-patterns)
|
|
28
|
+
8. [Examples](#examples)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Core Context System
|
|
33
|
+
|
|
34
|
+
### Context Class
|
|
35
|
+
|
|
36
|
+
A generic context class for managing shared state and event subscriptions.
|
|
37
|
+
|
|
38
|
+
**Type Definition:**
|
|
39
|
+
```typescript
|
|
40
|
+
class Context<D> {
|
|
41
|
+
constructor(name: string)
|
|
42
|
+
data: Partial<D>
|
|
43
|
+
registry: Set<string>
|
|
44
|
+
publish(key: keyof D, value: D[typeof key] | undefined): void
|
|
45
|
+
subscribe(key: keyof D, listener: (e: D[typeof key] | undefined) => void): () => void
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Parameters:**
|
|
50
|
+
- `D` - The shape of the data managed by the context
|
|
51
|
+
|
|
52
|
+
**Properties:**
|
|
53
|
+
- `name` - The name of the context (for debugging)
|
|
54
|
+
- `data` - The current data held by the context
|
|
55
|
+
- `registry` - Registry for tracking active keys
|
|
56
|
+
|
|
57
|
+
**Methods:**
|
|
58
|
+
- `publish(key, value)` - Publish a value to the context and notify subscribers if it changed
|
|
59
|
+
- `subscribe(key, listener)` - Subscribe to changes for a specific key, returns unsubscribe function
|
|
60
|
+
|
|
61
|
+
**Example:**
|
|
62
|
+
```typescript
|
|
63
|
+
interface UserData {
|
|
64
|
+
name: string;
|
|
65
|
+
age: number;
|
|
66
|
+
email: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const userContext = new Context<UserData>('user-context');
|
|
70
|
+
|
|
71
|
+
// Subscribe to changes
|
|
72
|
+
const unsubscribe = userContext.subscribe('name', (newName) => {
|
|
73
|
+
console.log('Name changed to:', newName);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Publish data
|
|
77
|
+
userContext.publish('name', 'John Doe');
|
|
78
|
+
userContext.publish('age', 30);
|
|
79
|
+
|
|
80
|
+
// Cleanup
|
|
81
|
+
unsubscribe();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### getContext
|
|
87
|
+
|
|
88
|
+
Get or create a memoized Context instance by name.
|
|
89
|
+
|
|
90
|
+
**Type Definition:**
|
|
91
|
+
```typescript
|
|
92
|
+
function getContext(name: string): Context<any>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Parameters:**
|
|
96
|
+
- `name` - The context name
|
|
97
|
+
|
|
98
|
+
**Returns:**
|
|
99
|
+
- The Context instance (memoized by name)
|
|
100
|
+
|
|
101
|
+
**Example:**
|
|
102
|
+
```typescript
|
|
103
|
+
const userCtx = getContext('user-context');
|
|
104
|
+
const settingsCtx = getContext('settings-context');
|
|
105
|
+
|
|
106
|
+
// Same name returns the same instance
|
|
107
|
+
const userCtx2 = getContext('user-context');
|
|
108
|
+
console.log(userCtx === userCtx2); // true
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### useDataContext
|
|
114
|
+
|
|
115
|
+
React hook to get a typed Context instance by name.
|
|
116
|
+
|
|
117
|
+
**Type Definition:**
|
|
118
|
+
```typescript
|
|
119
|
+
function useDataContext<D>(name?: string): Context<D>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Parameters:**
|
|
123
|
+
- `name` - The context name (default: "noname")
|
|
124
|
+
|
|
125
|
+
**Returns:**
|
|
126
|
+
- The typed Context instance
|
|
127
|
+
|
|
128
|
+
**Example:**
|
|
129
|
+
```typescript
|
|
130
|
+
interface AppState {
|
|
131
|
+
user: User;
|
|
132
|
+
theme: 'light' | 'dark';
|
|
133
|
+
isLoading: boolean;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function MyComponent() {
|
|
137
|
+
const ctx = useDataContext<AppState>('app-state');
|
|
138
|
+
|
|
139
|
+
// Now ctx is typed as Context<AppState>
|
|
140
|
+
return <div>Context ready</div>;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Data Source Hooks
|
|
147
|
+
|
|
148
|
+
### useDataSource
|
|
149
|
+
|
|
150
|
+
React hook to publish a value to the context when it changes.
|
|
151
|
+
|
|
152
|
+
**Type Definition:**
|
|
153
|
+
```typescript
|
|
154
|
+
function useDataSource<D, K extends keyof D>(
|
|
155
|
+
ctx: Context<D> | undefined,
|
|
156
|
+
key: K,
|
|
157
|
+
value: D[K] | undefined
|
|
158
|
+
): void
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Parameters:**
|
|
162
|
+
- `ctx` - The context instance
|
|
163
|
+
- `key` - The key to update
|
|
164
|
+
- `value` - The new value
|
|
165
|
+
|
|
166
|
+
**Example:**
|
|
167
|
+
```typescript
|
|
168
|
+
interface UserState {
|
|
169
|
+
profile: UserProfile;
|
|
170
|
+
preferences: UserPreferences;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function UserProvider({ userId }: { userId: string }) {
|
|
174
|
+
const ctx = useDataContext<UserState>('user-state');
|
|
175
|
+
const userProfile = useFetchUserProfile(userId);
|
|
176
|
+
const userPreferences = useFetchUserPreferences(userId);
|
|
177
|
+
|
|
178
|
+
// Automatically publish to context when values change
|
|
179
|
+
useDataSource(ctx, 'profile', userProfile);
|
|
180
|
+
useDataSource(ctx, 'preferences', userPreferences);
|
|
181
|
+
|
|
182
|
+
return <>{children}</>;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### useDataSourceMultiple
|
|
189
|
+
|
|
190
|
+
React hook to publish multiple values to the context.
|
|
191
|
+
|
|
192
|
+
**Type Definition:**
|
|
193
|
+
```typescript
|
|
194
|
+
function useDataSourceMultiple<D, T extends readonly (keyof D)[]>(
|
|
195
|
+
ctx: Context<D> | undefined,
|
|
196
|
+
...entries: { -readonly [P in keyof T]: [T[P], D[T[P]]] }
|
|
197
|
+
): void
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Parameters:**
|
|
201
|
+
- `ctx` - The context instance
|
|
202
|
+
- `entries` - Array of [key, value] pairs to update
|
|
203
|
+
|
|
204
|
+
**Example:**
|
|
205
|
+
```typescript
|
|
206
|
+
function AppProvider() {
|
|
207
|
+
const ctx = useDataContext<AppState>('app-state');
|
|
208
|
+
const user = useCurrentUser();
|
|
209
|
+
const theme = useTheme();
|
|
210
|
+
const isLoading = useLoadingState();
|
|
211
|
+
|
|
212
|
+
// Publish multiple values at once
|
|
213
|
+
useDataSourceMultiple(ctx,
|
|
214
|
+
['user', user],
|
|
215
|
+
['theme', theme],
|
|
216
|
+
['isLoading', isLoading]
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
return <>{children}</>;
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Data Subscription Hooks
|
|
226
|
+
|
|
227
|
+
### useDataSubscribe
|
|
228
|
+
|
|
229
|
+
React hook to subscribe to a context value, with optional debounce.
|
|
230
|
+
|
|
231
|
+
**Type Definition:**
|
|
232
|
+
```typescript
|
|
233
|
+
function useDataSubscribe<D, K extends keyof D>(
|
|
234
|
+
ctx: Context<D> | undefined,
|
|
235
|
+
key: K,
|
|
236
|
+
debounceTime?: number
|
|
237
|
+
): D[K] | undefined
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Parameters:**
|
|
241
|
+
- `ctx` - The context instance
|
|
242
|
+
- `key` - The key to subscribe to
|
|
243
|
+
- `debounceTime` - Debounce time in ms (default: 0)
|
|
244
|
+
|
|
245
|
+
**Returns:**
|
|
246
|
+
- The current value for the key
|
|
247
|
+
|
|
248
|
+
**Example:**
|
|
249
|
+
```typescript
|
|
250
|
+
function UserProfile() {
|
|
251
|
+
const ctx = useDataContext<UserState>('user-state');
|
|
252
|
+
|
|
253
|
+
// Subscribe to user profile changes
|
|
254
|
+
const profile = useDataSubscribe(ctx, 'profile');
|
|
255
|
+
|
|
256
|
+
// Subscribe with debouncing for frequently changing data
|
|
257
|
+
const searchQuery = useDataSubscribe(ctx, 'searchQuery', 300);
|
|
258
|
+
|
|
259
|
+
if (!profile) return <div>Loading...</div>;
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<div>
|
|
263
|
+
<h1>{profile.name}</h1>
|
|
264
|
+
<p>{profile.email}</p>
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### useDataSubscribeMultiple
|
|
273
|
+
|
|
274
|
+
React hook to subscribe to multiple context values.
|
|
275
|
+
|
|
276
|
+
**Type Definition:**
|
|
277
|
+
```typescript
|
|
278
|
+
function useDataSubscribeMultiple<D, K extends keyof D>(
|
|
279
|
+
ctx: Context<D> | undefined,
|
|
280
|
+
...keys: K[]
|
|
281
|
+
): Pick<D, K>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Parameters:**
|
|
285
|
+
- `ctx` - The context instance
|
|
286
|
+
- `keys` - Keys to subscribe to
|
|
287
|
+
|
|
288
|
+
**Returns:**
|
|
289
|
+
- An object with the current values for the keys
|
|
290
|
+
|
|
291
|
+
**Example:**
|
|
292
|
+
```typescript
|
|
293
|
+
function Dashboard() {
|
|
294
|
+
const ctx = useDataContext<AppState>('app-state');
|
|
295
|
+
|
|
296
|
+
// Subscribe to multiple values
|
|
297
|
+
const { user, theme, isLoading } = useDataSubscribeMultiple(
|
|
298
|
+
ctx,
|
|
299
|
+
'user',
|
|
300
|
+
'theme',
|
|
301
|
+
'isLoading'
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<div className={`dashboard ${theme}`}>
|
|
306
|
+
{isLoading && <Spinner />}
|
|
307
|
+
<h1>Welcome, {user?.name}</h1>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
### useDataSubscribeMultipleWithDebounce
|
|
316
|
+
|
|
317
|
+
React hook to subscribe to multiple context values with throttling.
|
|
318
|
+
|
|
319
|
+
**Type Definition:**
|
|
320
|
+
```typescript
|
|
321
|
+
function useDataSubscribeMultipleWithDebounce<D, K extends (keyof D)[]>(
|
|
322
|
+
ctx: Context<D> | undefined,
|
|
323
|
+
debounceTime?: number,
|
|
324
|
+
...keys: K
|
|
325
|
+
): { [i in keyof K]: D[K[i]] | undefined }
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Parameters:**
|
|
329
|
+
- `ctx` - The context instance
|
|
330
|
+
- `debounceTime` - Debounce time in ms (default: 50)
|
|
331
|
+
- `keys` - Keys to subscribe to
|
|
332
|
+
|
|
333
|
+
**Returns:**
|
|
334
|
+
- Array of current values for the keys
|
|
335
|
+
|
|
336
|
+
**Example:**
|
|
337
|
+
```typescript
|
|
338
|
+
function SearchResults() {
|
|
339
|
+
const ctx = useDataContext<SearchState>('search-state');
|
|
340
|
+
|
|
341
|
+
// Subscribe with debouncing for performance
|
|
342
|
+
const [query, filters, sortBy] = useDataSubscribeMultipleWithDebounce(
|
|
343
|
+
ctx,
|
|
344
|
+
200, // 200ms debounce
|
|
345
|
+
'query',
|
|
346
|
+
'filters',
|
|
347
|
+
'sortBy'
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const results = useSearchResults(query, filters, sortBy);
|
|
351
|
+
|
|
352
|
+
return <ResultsList results={results} />;
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
### useDataSubscribeWithTransform
|
|
359
|
+
|
|
360
|
+
React hook to subscribe to a context value and transform it before returning.
|
|
361
|
+
|
|
362
|
+
**Type Definition:**
|
|
363
|
+
```typescript
|
|
364
|
+
function useDataSubscribeWithTransform<D, K extends keyof D, E>(
|
|
365
|
+
ctx: Context<D> | undefined,
|
|
366
|
+
key: K,
|
|
367
|
+
transform: (e: D[K] | undefined) => E
|
|
368
|
+
): E
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Parameters:**
|
|
372
|
+
- `ctx` - The context instance
|
|
373
|
+
- `key` - The key to subscribe to
|
|
374
|
+
- `transform` - Function to transform the value
|
|
375
|
+
|
|
376
|
+
**Returns:**
|
|
377
|
+
- The transformed value
|
|
378
|
+
|
|
379
|
+
**Example:**
|
|
380
|
+
```typescript
|
|
381
|
+
function UserStats() {
|
|
382
|
+
const ctx = useDataContext<UserState>('user-state');
|
|
383
|
+
|
|
384
|
+
// Transform user data to display stats
|
|
385
|
+
const userStats = useDataSubscribeWithTransform(
|
|
386
|
+
ctx,
|
|
387
|
+
'profile',
|
|
388
|
+
(profile) => ({
|
|
389
|
+
totalPosts: profile?.posts?.length || 0,
|
|
390
|
+
joinedDate: profile?.createdAt ? new Date(profile.createdAt) : null,
|
|
391
|
+
isVerified: profile?.verified || false
|
|
392
|
+
})
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<div>
|
|
397
|
+
<p>Posts: {userStats.totalPosts}</p>
|
|
398
|
+
<p>Joined: {userStats.joinedDate?.toLocaleDateString()}</p>
|
|
399
|
+
{userStats.isVerified && <Badge>Verified</Badge>}
|
|
400
|
+
</div>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Root Context Factory
|
|
408
|
+
|
|
409
|
+
### createRootCtx
|
|
410
|
+
|
|
411
|
+
Factory that creates a headless "Root" component and companion hooks for a context namespace.
|
|
412
|
+
|
|
413
|
+
**Type Definition:**
|
|
414
|
+
```typescript
|
|
415
|
+
function createRootCtx<U extends object, V extends object>(
|
|
416
|
+
name: string,
|
|
417
|
+
useFn: (e: U) => V
|
|
418
|
+
): {
|
|
419
|
+
Root: React.FC<U>;
|
|
420
|
+
useCtxState: (e: U) => Context<V>;
|
|
421
|
+
useCtxStateStrict: (e: U) => Context<V>;
|
|
422
|
+
resolveCtxName: (e: U) => string;
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**Parameters:**
|
|
427
|
+
- `name` - Base name for the context
|
|
428
|
+
- `useFn` - Hook function that computes state from props
|
|
429
|
+
|
|
430
|
+
**Returns:**
|
|
431
|
+
- `Root` - Component to mount for providing the context
|
|
432
|
+
- `useCtxState` - Lenient consumer hook
|
|
433
|
+
- `useCtxStateStrict` - Strict consumer hook (throws if Root not mounted)
|
|
434
|
+
- `resolveCtxName` - Function to resolve context name from props
|
|
435
|
+
|
|
436
|
+
**Example:**
|
|
437
|
+
```typescript
|
|
438
|
+
// Define your state hook
|
|
439
|
+
function useUserState(props: { userId: string }) {
|
|
440
|
+
const [user, setUser] = useState(null);
|
|
441
|
+
const [loading, setLoading] = useState(true);
|
|
442
|
+
|
|
443
|
+
useEffect(() => {
|
|
444
|
+
fetchUser(props.userId).then(user => {
|
|
445
|
+
setUser(user);
|
|
446
|
+
setLoading(false);
|
|
447
|
+
});
|
|
448
|
+
}, [props.userId]);
|
|
449
|
+
|
|
450
|
+
return { user, loading };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Create the root context
|
|
454
|
+
const { Root: UserRoot, useCtxState: useUserCtxState } = createRootCtx(
|
|
455
|
+
'user-state',
|
|
456
|
+
useUserState
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
// Provider component
|
|
460
|
+
function UserProvider({ userId, children }: { userId: string; children: React.ReactNode }) {
|
|
461
|
+
return (
|
|
462
|
+
<>
|
|
463
|
+
<UserRoot userId={userId} />
|
|
464
|
+
{children}
|
|
465
|
+
</>
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Consumer component
|
|
470
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
471
|
+
const ctx = useUserCtxState({ userId });
|
|
472
|
+
const { user, loading } = useDataSubscribeMultiple(ctx, 'user', 'loading');
|
|
473
|
+
|
|
474
|
+
if (loading) return <div>Loading...</div>;
|
|
475
|
+
|
|
476
|
+
return <div>Hello, {user?.name}</div>;
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## Auto Context System
|
|
483
|
+
|
|
484
|
+
### AutoRootCtx
|
|
485
|
+
|
|
486
|
+
Component for automatic context management. Mount once at the app root to enable automatic Root instance management.
|
|
487
|
+
|
|
488
|
+
**Type Definition:**
|
|
489
|
+
```typescript
|
|
490
|
+
function AutoRootCtx({ Wrapper }: {
|
|
491
|
+
Wrapper?: React.ComponentType<{ children: React.ReactNode }>
|
|
492
|
+
}): JSX.Element
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Parameters:**
|
|
496
|
+
- `Wrapper` - Optional wrapper component (should act like ErrorBoundary)
|
|
497
|
+
|
|
498
|
+
**Example:**
|
|
499
|
+
```typescript
|
|
500
|
+
// At your app root
|
|
501
|
+
function App() {
|
|
502
|
+
return (
|
|
503
|
+
<>
|
|
504
|
+
<AutoRootCtx Wrapper={ErrorBoundary}/>
|
|
505
|
+
<Router>
|
|
506
|
+
<Routes>
|
|
507
|
+
<Route path="/user/:id" element={<UserPage />} />
|
|
508
|
+
</Routes>
|
|
509
|
+
</Router>
|
|
510
|
+
</>
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ErrorBoundary wrapper
|
|
515
|
+
function ErrorBoundary({ children }: { children: React.ReactNode }) {
|
|
516
|
+
// Your error boundary logic
|
|
517
|
+
return <>{children}</>;
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
### createAutoCtx
|
|
524
|
+
|
|
525
|
+
Bridges a Root context (from createRootCtx) to the global AutoRootCtx renderer.
|
|
526
|
+
|
|
527
|
+
**Type Definition:**
|
|
528
|
+
```typescript
|
|
529
|
+
function createAutoCtx<U extends object, V extends object>(
|
|
530
|
+
rootContext: ReturnType<typeof createRootCtx<U, V>>
|
|
531
|
+
): {
|
|
532
|
+
useCtxState: (e: U) => Context<V>;
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**Parameters:**
|
|
537
|
+
- `rootContext` - Return value from createRootCtx
|
|
538
|
+
|
|
539
|
+
**Returns:**
|
|
540
|
+
- `useCtxState` - Hook that automatically manages Root instances
|
|
541
|
+
|
|
542
|
+
**Example:**
|
|
543
|
+
```typescript
|
|
544
|
+
// Create auto context from root context
|
|
545
|
+
const { useCtxState: useUserCtxStateAuto } = createAutoCtx(
|
|
546
|
+
createRootCtx('user-state', useUserState)
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// Usage - no need to manually mount Root components
|
|
550
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
551
|
+
// This automatically manages the Root instance
|
|
552
|
+
const ctx = useUserCtxStateAuto({ userId });
|
|
553
|
+
const { user, loading } = useDataSubscribeMultiple(ctx, 'user', 'loading');
|
|
554
|
+
|
|
555
|
+
if (loading) return <div>Loading...</div>;
|
|
556
|
+
|
|
557
|
+
return <div>Hello, {user?.name}</div>;
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## Utility Hooks
|
|
564
|
+
|
|
565
|
+
### useArrayHash
|
|
566
|
+
|
|
567
|
+
A custom hook that computes a stable hash for an array of values.
|
|
568
|
+
|
|
569
|
+
**Type Definition:**
|
|
570
|
+
```typescript
|
|
571
|
+
function useArrayHash(e: any[]): string
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**Parameters:**
|
|
575
|
+
- `e` - The input array to hash
|
|
576
|
+
|
|
577
|
+
**Returns:**
|
|
578
|
+
- A string hash that updates when the array changes
|
|
579
|
+
|
|
580
|
+
**Example:**
|
|
581
|
+
```typescript
|
|
582
|
+
function OptimizedComponent({ items }: { items: any[] }) {
|
|
583
|
+
// Get stable hash for the array
|
|
584
|
+
const itemsHash = useArrayHash(items);
|
|
585
|
+
|
|
586
|
+
// Only recalculate when hash changes
|
|
587
|
+
const processedItems = useMemo(() => {
|
|
588
|
+
return items.map(item => expensiveProcessing(item));
|
|
589
|
+
}, [itemsHash]);
|
|
590
|
+
|
|
591
|
+
return <div>{processedItems.length} items processed</div>;
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
### useQuickSubscribe
|
|
598
|
+
|
|
599
|
+
Hook for efficiently subscribing to specific properties of a context's data object.
|
|
600
|
+
|
|
601
|
+
**Type Definition:**
|
|
602
|
+
```typescript
|
|
603
|
+
function useQuickSubscribe<D>(
|
|
604
|
+
ctx: Context<D> | undefined
|
|
605
|
+
): { [P in keyof D]?: D[P] | undefined }
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Parameters:**
|
|
609
|
+
- `ctx` - The context object containing data and a subscribe method
|
|
610
|
+
|
|
611
|
+
**Returns:**
|
|
612
|
+
- A proxy object that mirrors the context data, automatically subscribing to accessed properties
|
|
613
|
+
|
|
614
|
+
**Example:**
|
|
615
|
+
```typescript
|
|
616
|
+
function UserComponent() {
|
|
617
|
+
const ctx = useDataContext<UserState>('user-state');
|
|
618
|
+
|
|
619
|
+
// Only subscribes to properties you actually access
|
|
620
|
+
const { name, email } = useQuickSubscribe(ctx);
|
|
621
|
+
// Accessing 'name' and 'email' will subscribe to changes in those properties only
|
|
622
|
+
|
|
623
|
+
return (
|
|
624
|
+
<div>
|
|
625
|
+
<h1>{name}</h1>
|
|
626
|
+
<p>{email}</p>
|
|
627
|
+
{/* If you later access 'age', it will automatically subscribe to that too */}
|
|
628
|
+
</div>
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## Usage Patterns
|
|
636
|
+
|
|
637
|
+
### Basic Context Usage
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
// 1. Define your data interface
|
|
641
|
+
interface AppState {
|
|
642
|
+
user: User | null;
|
|
643
|
+
theme: 'light' | 'dark';
|
|
644
|
+
notifications: Notification[];
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// 2. Create and use context
|
|
648
|
+
function App() {
|
|
649
|
+
const ctx = useDataContext<AppState>('app-state');
|
|
650
|
+
|
|
651
|
+
// 3. Provide data
|
|
652
|
+
const user = useCurrentUser();
|
|
653
|
+
const theme = useTheme();
|
|
654
|
+
useDataSource(ctx, 'user', user);
|
|
655
|
+
useDataSource(ctx, 'theme', theme);
|
|
656
|
+
|
|
657
|
+
return <AppContent />;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// 4. Consume data
|
|
661
|
+
function AppContent() {
|
|
662
|
+
const ctx = useDataContext<AppState>('app-state');
|
|
663
|
+
const { user, theme } = useDataSubscribeMultiple(ctx, 'user', 'theme');
|
|
664
|
+
|
|
665
|
+
return <div className={`app ${theme}`}>Welcome, {user?.name}</div>;
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### Advanced Root Context Pattern
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
// 1. Create state hook
|
|
673
|
+
function useAppState(props: { initialTheme: string }) {
|
|
674
|
+
const [theme, setTheme] = useState(props.initialTheme);
|
|
675
|
+
const [user, setUser] = useState(null);
|
|
676
|
+
|
|
677
|
+
return { theme, user, setTheme, setUser };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// 2. Create root context
|
|
681
|
+
const { Root: AppRoot, useCtxState } = createRootCtx('app', useAppState);
|
|
682
|
+
|
|
683
|
+
// 3. Provider pattern
|
|
684
|
+
function AppProvider({ children }: { children: React.ReactNode }) {
|
|
685
|
+
return (
|
|
686
|
+
<>
|
|
687
|
+
<AppRoot initialTheme="light" />
|
|
688
|
+
{children}
|
|
689
|
+
</>
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// 4. Consumer hook
|
|
694
|
+
function useAppContext() {
|
|
695
|
+
return useCtxState({ initialTheme: 'light' });
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Auto Context Pattern
|
|
700
|
+
|
|
701
|
+
```typescript
|
|
702
|
+
// 1. Setup auto context once
|
|
703
|
+
const { useCtxState: useAppState } = createAutoCtx(
|
|
704
|
+
createRootCtx('app-auto', useAppStateLogic)
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
// 2. Mount AutoRootCtx at app root
|
|
708
|
+
function App() {
|
|
709
|
+
return (
|
|
710
|
+
<AutoRootCtx Wrapper={ErrorBoundary}>
|
|
711
|
+
<MyApp />
|
|
712
|
+
</AutoRootCtx>
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// 3. Use anywhere without manual Root mounting
|
|
717
|
+
function AnyComponent() {
|
|
718
|
+
const ctx = useAppState({ config: 'production' });
|
|
719
|
+
const data = useQuickSubscribe(ctx);
|
|
720
|
+
|
|
721
|
+
return <div>{data.someProperty}</div>;
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
---
|
|
726
|
+
|
|
727
|
+
## Examples
|
|
728
|
+
|
|
729
|
+
### Complete Todo App Example
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
// Types
|
|
733
|
+
interface TodoState {
|
|
734
|
+
todos: Todo[];
|
|
735
|
+
filter: 'all' | 'active' | 'completed';
|
|
736
|
+
newTodoText: string;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
interface Todo {
|
|
740
|
+
id: string;
|
|
741
|
+
text: string;
|
|
742
|
+
completed: boolean;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// State hook
|
|
746
|
+
function useTodoState() {
|
|
747
|
+
const [todos, setTodos] = useState<Todo[]>([]);
|
|
748
|
+
const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all');
|
|
749
|
+
const [newTodoText, setNewTodoText] = useState('');
|
|
750
|
+
|
|
751
|
+
const addTodo = useCallback((text: string) => {
|
|
752
|
+
const newTodo: Todo = {
|
|
753
|
+
id: Date.now().toString(),
|
|
754
|
+
text,
|
|
755
|
+
completed: false
|
|
756
|
+
};
|
|
757
|
+
setTodos(prev => [...prev, newTodo]);
|
|
758
|
+
setNewTodoText('');
|
|
759
|
+
}, []);
|
|
760
|
+
|
|
761
|
+
const toggleTodo = useCallback((id: string) => {
|
|
762
|
+
setTodos(prev => prev.map(todo =>
|
|
763
|
+
todo.id === id ? { ...todo, completed: !todo.completed } : todo
|
|
764
|
+
));
|
|
765
|
+
}, []);
|
|
766
|
+
|
|
767
|
+
return {
|
|
768
|
+
todos,
|
|
769
|
+
filter,
|
|
770
|
+
newTodoText,
|
|
771
|
+
setFilter,
|
|
772
|
+
setNewTodoText,
|
|
773
|
+
addTodo,
|
|
774
|
+
toggleTodo
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Auto context setup
|
|
779
|
+
const { useCtxState: useTodoContext } = createAutoCtx(
|
|
780
|
+
createRootCtx('todo-app', useTodoState)
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
// App component
|
|
784
|
+
function TodoApp() {
|
|
785
|
+
return (
|
|
786
|
+
<AutoRootCtx>
|
|
787
|
+
<div className="todo-app">
|
|
788
|
+
<TodoInput />
|
|
789
|
+
<TodoList />
|
|
790
|
+
<TodoFilters />
|
|
791
|
+
</div>
|
|
792
|
+
</AutoRootCtx>
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Input component
|
|
797
|
+
function TodoInput() {
|
|
798
|
+
const ctx = useTodoContext();
|
|
799
|
+
const { newTodoText, addTodo, setNewTodoText } = useQuickSubscribe(ctx);
|
|
800
|
+
|
|
801
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
802
|
+
e.preventDefault();
|
|
803
|
+
if (newTodoText.trim()) {
|
|
804
|
+
addTodo(newTodoText.trim());
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
return (
|
|
809
|
+
<form onSubmit={handleSubmit}>
|
|
810
|
+
<input
|
|
811
|
+
value={newTodoText}
|
|
812
|
+
onChange={(e) => setNewTodoText(e.target.value)}
|
|
813
|
+
placeholder="Add a new todo..."
|
|
814
|
+
/>
|
|
815
|
+
<button type="submit">Add</button>
|
|
816
|
+
</form>
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// List component
|
|
821
|
+
function TodoList() {
|
|
822
|
+
const ctx = useTodoContext();
|
|
823
|
+
const { todos, filter } = useDataSubscribeMultiple(ctx, 'todos', 'filter');
|
|
824
|
+
|
|
825
|
+
const filteredTodos = useMemo(() => {
|
|
826
|
+
switch (filter) {
|
|
827
|
+
case 'active':
|
|
828
|
+
return todos.filter(todo => !todo.completed);
|
|
829
|
+
case 'completed':
|
|
830
|
+
return todos.filter(todo => todo.completed);
|
|
831
|
+
default:
|
|
832
|
+
return todos;
|
|
833
|
+
}
|
|
834
|
+
}, [todos, filter]);
|
|
835
|
+
|
|
836
|
+
return (
|
|
837
|
+
<ul className="todo-list">
|
|
838
|
+
{filteredTodos.map(todo => (
|
|
839
|
+
<TodoItem key={todo.id} todo={todo} />
|
|
840
|
+
))}
|
|
841
|
+
</ul>
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Todo item component
|
|
846
|
+
function TodoItem({ todo }: { todo: Todo }) {
|
|
847
|
+
const ctx = useTodoContext();
|
|
848
|
+
const { toggleTodo } = useQuickSubscribe(ctx);
|
|
849
|
+
|
|
850
|
+
return (
|
|
851
|
+
<li className={`todo-item ${todo.completed ? 'completed' : ''}`}>
|
|
852
|
+
<input
|
|
853
|
+
type="checkbox"
|
|
854
|
+
checked={todo.completed}
|
|
855
|
+
onChange={() => toggleTodo(todo.id)}
|
|
856
|
+
/>
|
|
857
|
+
<span>{todo.text}</span>
|
|
858
|
+
</li>
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Filter component
|
|
863
|
+
function TodoFilters() {
|
|
864
|
+
const ctx = useTodoContext();
|
|
865
|
+
const { filter, setFilter } = useQuickSubscribe(ctx);
|
|
866
|
+
|
|
867
|
+
return (
|
|
868
|
+
<div className="todo-filters">
|
|
869
|
+
{(['all', 'active', 'completed'] as const).map(filterType => (
|
|
870
|
+
<button
|
|
871
|
+
key={filterType}
|
|
872
|
+
className={filter === filterType ? 'active' : ''}
|
|
873
|
+
onClick={() => setFilter(filterType)}
|
|
874
|
+
>
|
|
875
|
+
{filterType}
|
|
876
|
+
</button>
|
|
877
|
+
))}
|
|
878
|
+
</div>
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
This comprehensive documentation covers all exported APIs from the react-state-custom library with detailed descriptions, type information, and practical examples.
|