react-action-z 0.0.1
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/LICENSE +21 -0
- package/README.md +490 -0
- package/build/core/client.d.ts +5 -0
- package/build/core/middleware.d.ts +6 -0
- package/build/core/store.d.ts +9 -0
- package/build/hooks/index.d.ts +3 -0
- package/build/hooks/useAction.d.ts +21 -0
- package/build/hooks/useActionStatus.d.ts +5 -0
- package/build/hooks/useGlobalAction.d.ts +7 -0
- package/build/index.d.ts +4 -0
- package/build/index.esm.js +1 -0
- package/build/index.js +1 -0
- package/build/middleware/logger.d.ts +2 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 delpikye-v
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# โก react-action-z
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/react-action-z) 
|
|
4
|
+
|
|
5
|
+
<a href="https://codesandbox.io/p/sandbox/hgk4hx" target="_blank">LIVE EXAMPLE</a>
|
|
6
|
+
|
|
7
|
+
Unified async action layer for React.
|
|
8
|
+
|
|
9
|
+
> Run async functions as **actions**, not boilerplate.
|
|
10
|
+
> Actions run only when you call run() โ never automatically.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Why react-action-z?
|
|
15
|
+
|
|
16
|
+
- โก Unified async actions
|
|
17
|
+
- ๐ง Built-in loading / error state
|
|
18
|
+
- ๐ Retry support
|
|
19
|
+
- ๐งฉ Middleware system
|
|
20
|
+
- ๐ Global action state
|
|
21
|
+
- ๐งพ Full TypeScript support
|
|
22
|
+
|
|
23
|
+
Traditional async UI code:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const [loading, setLoading] = useState(false)
|
|
27
|
+
const [error, setError] = useState(null)
|
|
28
|
+
|
|
29
|
+
async function login(data) {
|
|
30
|
+
try {
|
|
31
|
+
setLoading(true)
|
|
32
|
+
const result = await api.login(data)
|
|
33
|
+
} catch (err) {
|
|
34
|
+
setError(err)
|
|
35
|
+
} finally {
|
|
36
|
+
setLoading(false)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
React Action:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const login = useAction(api.login)
|
|
45
|
+
|
|
46
|
+
await login.run(data)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
UI state automatically available:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
login.loading
|
|
53
|
+
login.data
|
|
54
|
+
login.error
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
# Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install react-action-z
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
# Basic Usage
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { useAction } from "react-action-z"
|
|
71
|
+
|
|
72
|
+
const login = useAction(api.login)
|
|
73
|
+
|
|
74
|
+
await login.run({
|
|
75
|
+
email,
|
|
76
|
+
password
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
State is automatically managed:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
login.loading
|
|
84
|
+
login.data
|
|
85
|
+
login.error
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
UI example:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<button disabled={login.loading}>
|
|
92
|
+
{login.loading ? "Loading..." : "Login"}
|
|
93
|
+
</button>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
# Example with JSONPlaceholder
|
|
99
|
+
|
|
100
|
+
Example using the free API from JSONPlaceholder.
|
|
101
|
+
|
|
102
|
+
Fetch a user from:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
https://jsonplaceholder.typicode.com/users/1
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## API
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
async function fetchUser(id: number) {
|
|
114
|
+
const res = await fetch(
|
|
115
|
+
`https://jsonplaceholder.typicode.com/users/${id}`
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if (!res.ok) {
|
|
119
|
+
throw new Error("Failed to fetch user")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return res.json()
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## React Component
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import React from "react"
|
|
132
|
+
import { useAction } from "react-action-z"
|
|
133
|
+
|
|
134
|
+
async function fetchUser(id: number) {
|
|
135
|
+
const res = await fetch(
|
|
136
|
+
`https://jsonplaceholder.typicode.com/users/${id}`
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
throw new Error("Failed to fetch user")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return res.json()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export default function UserExample() {
|
|
147
|
+
|
|
148
|
+
const getUser = useAction(fetchUser)
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div>
|
|
152
|
+
|
|
153
|
+
<button
|
|
154
|
+
onClick={() => getUser.run(1)}
|
|
155
|
+
disabled={getUser.loading}
|
|
156
|
+
>
|
|
157
|
+
{getUser.loading ? "Loading..." : "Load User"}
|
|
158
|
+
</button>
|
|
159
|
+
|
|
160
|
+
{getUser.error && (
|
|
161
|
+
<p>Error loading user</p>
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
{getUser.data && (
|
|
165
|
+
<div style={{ marginTop: 20 }}>
|
|
166
|
+
<h3>{getUser.data.name}</h3>
|
|
167
|
+
<p>{getUser.data.email}</p>
|
|
168
|
+
<p>{getUser.data.phone}</p>
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
171
|
+
|
|
172
|
+
</div>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Result
|
|
180
|
+
|
|
181
|
+
Click the button:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
Load User
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The action automatically manages:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
loading
|
|
191
|
+
data
|
|
192
|
+
error
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Example returned data:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"id": 1,
|
|
200
|
+
"name": "Leanne Graham",
|
|
201
|
+
"username": "Bret",
|
|
202
|
+
"email": "Sincere@april.biz",
|
|
203
|
+
"phone": "1-770-736-8031"
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
# Action API
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
const action = useAction(fn, options)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Return value:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
action.run(...)
|
|
219
|
+
action.loading
|
|
220
|
+
action.data
|
|
221
|
+
action.error
|
|
222
|
+
action.reset()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
# Options
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
useAction(api.login, {
|
|
231
|
+
retry: 2,
|
|
232
|
+
strategy: "replace",
|
|
233
|
+
key: "login",
|
|
234
|
+
optimistic: (data) => {},
|
|
235
|
+
onSuccess: (data) => {},
|
|
236
|
+
onError: (err) => {}
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
| option | description |
|
|
241
|
+
|-------------|---------------------|
|
|
242
|
+
| retry | retry on failure |
|
|
243
|
+
| strategy | concurrency control |
|
|
244
|
+
| key | global action key |
|
|
245
|
+
| optimistic | optimistic update |
|
|
246
|
+
| onSuccess | success callback |
|
|
247
|
+
| onError | error callback |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
# Concurrency Strategy
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
useAction(api.save, {
|
|
255
|
+
strategy: "ignore"
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
| strategy | behavior |
|
|
260
|
+
|----------|-----------------------------------|
|
|
261
|
+
| replace | only latest result updates state |
|
|
262
|
+
| ignore | ignore if running |
|
|
263
|
+
| parallel | allow multiple runs |
|
|
264
|
+
|
|
265
|
+
Example:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const save = useAction(api.save, {
|
|
269
|
+
strategy: "ignore"
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Prevent double submit.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
# Retry
|
|
278
|
+
|
|
279
|
+
Automatically retry failed requests.
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const fetchUser = useAction(api.fetchUser, {
|
|
283
|
+
retry: 2
|
|
284
|
+
})
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Behavior:
|
|
288
|
+
|
|
289
|
+
```text
|
|
290
|
+
attempt 1
|
|
291
|
+
attempt 2
|
|
292
|
+
attempt 3
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
# Optimistic Update
|
|
298
|
+
|
|
299
|
+
Apply UI update before request finishes.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
const updateUser = useAction(api.updateUser, {
|
|
303
|
+
optimistic: (data) => {
|
|
304
|
+
setUser(data)
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
This improves UI responsiveness.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
# Global Action State
|
|
314
|
+
|
|
315
|
+
Actions can share state across components.
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
import { useAction } from "react-action-z"
|
|
319
|
+
|
|
320
|
+
const login = useAction(api.login, {
|
|
321
|
+
key: "login"
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
export function LoginButton() {
|
|
325
|
+
return (
|
|
326
|
+
<button
|
|
327
|
+
disabled={login.loading}
|
|
328
|
+
onClick={() => login.run({ email, password })}
|
|
329
|
+
>
|
|
330
|
+
{login.loading ? "Logging in..." : "Login"}
|
|
331
|
+
</button>
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Access anywhere:
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
// GlobalSpinner.tsx
|
|
340
|
+
|
|
341
|
+
import { useGlobalAction } from "react-action-z"
|
|
342
|
+
|
|
343
|
+
export function GlobalSpinner() {
|
|
344
|
+
|
|
345
|
+
const login = useGlobalAction("login")
|
|
346
|
+
|
|
347
|
+
if (!login.loading) return null
|
|
348
|
+
|
|
349
|
+
return <div>Authenticating...</div>
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Result:
|
|
354
|
+
- LoginButton triggers action
|
|
355
|
+
- GlobalSpinner reacts automatically
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
# Middleware
|
|
360
|
+
|
|
361
|
+
React Action supports middleware similar to Redux.
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
import { createActionClient } from "react-action-z"
|
|
365
|
+
|
|
366
|
+
createActionClient({
|
|
367
|
+
middleware: [
|
|
368
|
+
async (context, next) => {
|
|
369
|
+
console.log("action:", context.key)
|
|
370
|
+
return next()
|
|
371
|
+
}
|
|
372
|
+
]
|
|
373
|
+
})
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Middleware context:
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
{
|
|
380
|
+
key?: string
|
|
381
|
+
args: any[]
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Use cases:
|
|
386
|
+
|
|
387
|
+
- logging
|
|
388
|
+
- analytics
|
|
389
|
+
- metrics
|
|
390
|
+
- debugging
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
# Reset State
|
|
395
|
+
|
|
396
|
+
Reset action state manually.
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
login.reset()
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Result:
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
{
|
|
406
|
+
loading: false
|
|
407
|
+
data: null
|
|
408
|
+
error: null
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
# Example
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
const createPost = useAction(api.createPost, {
|
|
418
|
+
retry: 1
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
async function handleSubmit(data) {
|
|
422
|
+
await createPost.run(data)
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
UI:
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
<button disabled={createPost.loading}>
|
|
430
|
+
Publish
|
|
431
|
+
</button>
|
|
432
|
+
|
|
433
|
+
{createPost.error && <p>Error</p>}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
# Comparison
|
|
439
|
+
|
|
440
|
+
| Criteria | react-action-z | React state |
|
|
441
|
+
|---------------------|----------------|-------------|
|
|
442
|
+
| Async state | โ
| manual |
|
|
443
|
+
| Retry | โ
| manual |
|
|
444
|
+
| Global action state | โ
| โ |
|
|
445
|
+
| Middleware | โ
| โ |
|
|
446
|
+
| Optimistic update | โ
| manual |
|
|
447
|
+
| Boilerplate | minimal | high |
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
# Architecture
|
|
452
|
+
|
|
453
|
+
```text
|
|
454
|
+
Component
|
|
455
|
+
โ
|
|
456
|
+
useAction()
|
|
457
|
+
โ
|
|
458
|
+
Action Runner
|
|
459
|
+
โ
|
|
460
|
+
Middleware
|
|
461
|
+
โ
|
|
462
|
+
Async Function
|
|
463
|
+
โ
|
|
464
|
+
State Update
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
# Philosophy
|
|
470
|
+
|
|
471
|
+
React Action treats async functions as **first-class UI actions**.
|
|
472
|
+
|
|
473
|
+
Instead of manually managing:
|
|
474
|
+
|
|
475
|
+
- loading
|
|
476
|
+
- error
|
|
477
|
+
- retries
|
|
478
|
+
- concurrency
|
|
479
|
+
|
|
480
|
+
You simply run actions:
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
action.run()
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
# License
|
|
489
|
+
|
|
490
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type MiddlewareContext = {
|
|
2
|
+
key?: string;
|
|
3
|
+
args: any[];
|
|
4
|
+
};
|
|
5
|
+
export type Middleware = (context: MiddlewareContext, next: () => Promise<any>) => Promise<any>;
|
|
6
|
+
export declare function runMiddleware(context: MiddlewareContext, middleware: Middleware[], fn: () => Promise<any>): Promise<any>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type ActionState<TResult> = {
|
|
2
|
+
loading: boolean;
|
|
3
|
+
data: TResult | null;
|
|
4
|
+
error: any;
|
|
5
|
+
};
|
|
6
|
+
export declare function setGlobalState(key: string, state: ActionState<any>): void;
|
|
7
|
+
export declare function subscribe(key: string, fn: Function): () => void;
|
|
8
|
+
export declare function getGlobalState(key: string): ActionState<any> | undefined;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Middleware } from "../core/middleware";
|
|
2
|
+
type AsyncFn<TArgs extends any[], TResult> = (...args: TArgs) => Promise<TResult>;
|
|
3
|
+
type Strategy = "replace" | "ignore" | "parallel";
|
|
4
|
+
type ActionOptions<TArgs extends any[], TResult> = {
|
|
5
|
+
key?: string;
|
|
6
|
+
retry?: number;
|
|
7
|
+
retryDelay?: number;
|
|
8
|
+
strategy?: Strategy;
|
|
9
|
+
optimistic?: (...args: TArgs) => void;
|
|
10
|
+
middleware?: Middleware[];
|
|
11
|
+
onSuccess?: (data: TResult) => void;
|
|
12
|
+
onError?: (err: any) => void;
|
|
13
|
+
};
|
|
14
|
+
export declare function useAction<TArgs extends any[], TResult>(fn: AsyncFn<TArgs, TResult>, options?: ActionOptions<TArgs, TResult>): {
|
|
15
|
+
loading: boolean;
|
|
16
|
+
data: TResult | null;
|
|
17
|
+
error: any;
|
|
18
|
+
run: (...args: TArgs) => Promise<any>;
|
|
19
|
+
reset: () => void;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useState as r,useRef as t,useCallback as n,useEffect as e}from"react";let o=[];function a(r){o=r.middleware||[]}const c=new Map,l=new Map;function i(r,t){c.set(r,t);const n=l.get(r);n&&n.forEach(r=>r(t))}async function u(r,t,n,e){try{return await r(...t)}catch(a){if(n>0)return await(o=e,new Promise(r=>setTimeout(r,o))),u(r,t,n-1,e);throw a}var o}function s(e,a={}){const{key:c,retry:l=0,retryDelay:s=500,strategy:d="replace",optimistic:g,middleware:f=[],onSuccess:y,onError:w}=a,[m,p]=r({loading:!1,data:null,error:null}),h=t(!1),k=t(0),D=n(async(...r)=>{if("ignore"===d&&h.current)return;const t=++k.current;"parallel"!==d&&(h.current=!0),g?.(...r),p(r=>({...r,loading:!0,error:null}));const n={key:c,args:r};try{const a=await function(r,t,n){let e=-1;const o=a=>{if(a<=e)return Promise.reject();e=a;const c=t[a];return c?c(r,()=>o(a+1)):n()};return o(0)}(n,[...o,...f],()=>u(e,r,l,s));if("replace"!==d||t===k.current){const r={loading:!1,data:a,error:null};p(r),c&&i(c,r),y?.(a)}return a}catch(r){if("replace"!==d||t===k.current){const t={loading:!1,data:null,error:r};p(t),c&&i(c,t),w?.(r)}throw r}finally{"parallel"!==d&&(h.current=!1)}},[e,l,s,d,g,f,c]),E=n(()=>{const r={loading:!1,data:null,error:null};p(r),c&&i(c,r)},[c]);return{loading:m.loading,data:m.data,error:m.error,run:D,reset:E}}function d(r,t){return t?r[t]:{loading:r.loading,data:r.data,error:r.error}}function g(t){const[n,o]=r(function(r){return c.get(r)}(t)||{loading:!1,data:null,error:null});return e(()=>function(r,t){return l.has(r)||l.set(r,new Set),l.get(r).add(t),()=>{l.get(r).delete(t)}}(t,o),[t]),n}function f(){return async(r,t)=>{const n=Date.now();console.log("[react-action] start:",r.key,r.args);try{const e=await t();return console.log("[react-action] success:",r.key,"time:",Date.now()-n,"ms"),e}catch(t){throw console.error("[react-action] error:",r.key,t),t}}}export{a as createActionClient,f as logger,s as useAction,d as useActionStatus,g as useGlobalAction};
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var r=require("react");let t=[];const e=new Map,n=new Map;function a(r,t){e.set(r,t);const a=n.get(r);a&&a.forEach(r=>r(t))}async function o(r,t,e,n){try{return await r(...t)}catch(c){if(e>0)return await(a=n,new Promise(r=>setTimeout(r,a))),o(r,t,e-1,n);throw c}var a}exports.createActionClient=function(r){t=r.middleware||[]},exports.logger=function(){return async(r,t)=>{const e=Date.now();console.log("[react-action] start:",r.key,r.args);try{const n=await t();return console.log("[react-action] success:",r.key,"time:",Date.now()-e,"ms"),n}catch(t){throw console.error("[react-action] error:",r.key,t),t}}},exports.useAction=function(e,n={}){const{key:c,retry:l=0,retryDelay:s=500,strategy:u="replace",optimistic:i,middleware:d=[],onSuccess:g,onError:f}=n,[y,w]=r.useState({loading:!1,data:null,error:null}),p=r.useRef(!1),h=r.useRef(0),m=r.useCallback(async(...r)=>{if("ignore"===u&&p.current)return;const n=++h.current;"parallel"!==u&&(p.current=!0),i?.(...r),w(r=>({...r,loading:!0,error:null}));const y={key:c,args:r};try{const i=await function(r,t,e){let n=-1;const a=o=>{if(o<=n)return Promise.reject();n=o;const c=t[o];return c?c(r,()=>a(o+1)):e()};return a(0)}(y,[...t,...d],()=>o(e,r,l,s));if("replace"!==u||n===h.current){const r={loading:!1,data:i,error:null};w(r),c&&a(c,r),g?.(i)}return i}catch(r){if("replace"!==u||n===h.current){const t={loading:!1,data:null,error:r};w(t),c&&a(c,t),f?.(r)}throw r}finally{"parallel"!==u&&(p.current=!1)}},[e,l,s,u,i,d,c]),k=r.useCallback(()=>{const r={loading:!1,data:null,error:null};w(r),c&&a(c,r)},[c]);return{loading:y.loading,data:y.data,error:y.error,run:m,reset:k}},exports.useActionStatus=function(r,t){return t?r[t]:{loading:r.loading,data:r.data,error:r.error}},exports.useGlobalAction=function(t){const[a,o]=r.useState(function(r){return e.get(r)}(t)||{loading:!1,data:null,error:null});return r.useEffect(()=>function(r,t){return n.has(r)||n.set(r,new Set),n.get(r).add(t),()=>{n.get(r).delete(t)}}(t,o),[t]),a};
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-action-z",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Turn async functions into UI actions. Handle loading, error, retry, and concurrency automatically.",
|
|
5
|
+
"author": "Delpi.Kye",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
|
|
9
|
+
"main": "./build/index.js",
|
|
10
|
+
"module": "./build/index.esm.js",
|
|
11
|
+
"types": "./build/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./build/index.d.ts",
|
|
15
|
+
"import": "./build/index.esm.js",
|
|
16
|
+
"require": "./build/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
"files": [
|
|
21
|
+
"build"
|
|
22
|
+
],
|
|
23
|
+
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"scripts": {
|
|
26
|
+
"clean": "rimraf build",
|
|
27
|
+
"build": "rimraf build && rollup -c",
|
|
28
|
+
"watch": "rollup -c -w",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=16"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/delpikye-v/react-action-z.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/delpikye-v/react-action-z#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/delpikye-v/react-action-z/issues"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"react",
|
|
44
|
+
"react-hooks",
|
|
45
|
+
"async-actions",
|
|
46
|
+
"react-async",
|
|
47
|
+
"react-mutation",
|
|
48
|
+
"react-state",
|
|
49
|
+
"react-data",
|
|
50
|
+
"async-state",
|
|
51
|
+
"optimistic-ui",
|
|
52
|
+
"react-utils",
|
|
53
|
+
"react-library",
|
|
54
|
+
"frontend",
|
|
55
|
+
"typescript"
|
|
56
|
+
],
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"react": ">=18",
|
|
59
|
+
"react-dom": ">=18"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
|
64
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
65
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
66
|
+
"@types/react": "^18.0.0",
|
|
67
|
+
"@types/react-dom": "^18.0.0",
|
|
68
|
+
"react": "^18.0.0",
|
|
69
|
+
"react-dom": "^18.0.0",
|
|
70
|
+
"rimraf": "^5.0.5",
|
|
71
|
+
"rollup": "^3.29.4",
|
|
72
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
73
|
+
"rollup-plugin-typescript2": "^0.36.0",
|
|
74
|
+
"tslib": "^2.6.2",
|
|
75
|
+
"typescript": "^5.0.0"
|
|
76
|
+
},
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public"
|
|
79
|
+
}
|
|
80
|
+
}
|