@okyrychenko-dev/react-action-guard 1.0.0 → 1.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/README.md +75 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,25 +4,18 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@okyrychenko-dev/react-action-guard)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- Conditional blocking based on application state
|
|
20
|
-
- Async action wrapping with timeout support
|
|
21
|
-
- Advanced middleware system for analytics, logging, and performance monitoring
|
|
22
|
-
- TypeScript support with full type safety (including optional type-safe scopes)
|
|
23
|
-
- Built on Zustand for efficient state management
|
|
24
|
-
- Tree-shakeable - import only what you need
|
|
25
|
-
- Hooks-based API
|
|
7
|
+
> Coordinate UI blocking across async actions and competing interactions in React
|
|
8
|
+
|
|
9
|
+
`react-action-guard` helps you prevent duplicate submits, conflicting actions, and stale UI interactions without scattering local `isLoading` flags across components.
|
|
10
|
+
|
|
11
|
+
## Why Use It
|
|
12
|
+
|
|
13
|
+
- Wrap async work with `useAsyncAction` and get blocker cleanup automatically
|
|
14
|
+
- Coordinate blocking by `scope` across unrelated components
|
|
15
|
+
- Inspect current blockers with `useIsBlocked` and `useBlockingInfo`
|
|
16
|
+
- Isolate state per provider for SSR, tests, and micro-frontends
|
|
17
|
+
- Extend lifecycle events with optional middleware
|
|
18
|
+
- Add advanced hooks for scheduled, confirmable, or conditional flows when needed
|
|
26
19
|
|
|
27
20
|
## Installation
|
|
28
21
|
|
|
@@ -48,15 +41,43 @@ If you previously imported `createShallowStore`, `createStoreToolkit`, `createSt
|
|
|
48
41
|
## Quick Start
|
|
49
42
|
|
|
50
43
|
```jsx
|
|
51
|
-
import {
|
|
44
|
+
import { useAsyncAction, useIsBlocked } from "@okyrychenko-dev/react-action-guard";
|
|
52
45
|
|
|
53
|
-
function
|
|
54
|
-
const
|
|
46
|
+
function SaveButton() {
|
|
47
|
+
const isSaving = useIsBlocked("form");
|
|
48
|
+
const runSave = useAsyncAction("save-profile", "form");
|
|
49
|
+
|
|
50
|
+
const handleClick = async () => {
|
|
51
|
+
await runSave(async () => {
|
|
52
|
+
await api.saveProfile();
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
55
|
|
|
56
|
-
return <button disabled={
|
|
56
|
+
return <button onClick={handleClick} disabled={isSaving}>Save</button>;
|
|
57
57
|
}
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
## Core Concepts
|
|
61
|
+
|
|
62
|
+
- `scope` lets you coordinate blocking across components like `"form"`, `"navigation"`, or `"checkout"`
|
|
63
|
+
- `useAsyncAction` is the fastest path for async workflows
|
|
64
|
+
- `useBlocker` is the lower-level hook when you already have your own boolean state
|
|
65
|
+
- `UIBlockingProvider` gives you isolated state instead of the default global store
|
|
66
|
+
|
|
67
|
+
## Core Use Cases
|
|
68
|
+
|
|
69
|
+
### Prevent duplicate async actions
|
|
70
|
+
|
|
71
|
+
Use `useAsyncAction` when a button, mutation, or workflow should block while work is in flight.
|
|
72
|
+
|
|
73
|
+
### Coordinate multiple components
|
|
74
|
+
|
|
75
|
+
Use shared scopes when one component starts work and another component should react by disabling UI or showing blocker details.
|
|
76
|
+
|
|
77
|
+
### Isolate blocking domains
|
|
78
|
+
|
|
79
|
+
Use `UIBlockingProvider` when each request, test, or micro-frontend should get its own store instance.
|
|
80
|
+
|
|
60
81
|
## Documentation
|
|
61
82
|
|
|
62
83
|
📚 **Interactive Storybook Documentation** - Run locally to explore live examples and detailed guides for all hooks
|
|
@@ -71,6 +92,15 @@ npm run storybook
|
|
|
71
92
|
|
|
72
93
|
### Hooks
|
|
73
94
|
|
|
95
|
+
#### Core hooks
|
|
96
|
+
|
|
97
|
+
Start with these first:
|
|
98
|
+
|
|
99
|
+
- `useAsyncAction`
|
|
100
|
+
- `useBlocker`
|
|
101
|
+
- `useIsBlocked`
|
|
102
|
+
- `useBlockingInfo`
|
|
103
|
+
|
|
74
104
|
#### `useBlocker(blockerId, config, isActive?)`
|
|
75
105
|
|
|
76
106
|
Automatically adds a blocker when the component mounts and removes it on unmount.
|
|
@@ -235,9 +265,18 @@ function ApiComponent() {
|
|
|
235
265
|
}
|
|
236
266
|
```
|
|
237
267
|
|
|
268
|
+
#### Advanced hooks
|
|
269
|
+
|
|
270
|
+
These hooks are useful, but they are not the main onboarding path for most apps:
|
|
271
|
+
|
|
272
|
+
- `useConfirmableBlocker` for confirmation flows
|
|
273
|
+
- `useScheduledBlocker` for time-window blocking
|
|
274
|
+
- `useConditionalBlocker` for polling-based conditional synchronization
|
|
275
|
+
|
|
238
276
|
#### `useConfirmableBlocker(blockerId, config)`
|
|
239
277
|
|
|
240
278
|
Creates a confirmable action with UI blocking while the dialog is open or the action is running.
|
|
279
|
+
Use it for advanced confirmation flows after the core hooks are already a good fit.
|
|
241
280
|
|
|
242
281
|
**Parameters:**
|
|
243
282
|
|
|
@@ -302,6 +341,7 @@ function UnsavedChangesGuard({ discardChanges }) {
|
|
|
302
341
|
#### `useScheduledBlocker(blockerId, config)`
|
|
303
342
|
|
|
304
343
|
Blocks UI during a scheduled time period or maintenance window.
|
|
344
|
+
Use it when blocking is driven by time windows rather than user-triggered async work.
|
|
305
345
|
|
|
306
346
|
**Parameters:**
|
|
307
347
|
|
|
@@ -342,6 +382,7 @@ function MaintenanceWindow() {
|
|
|
342
382
|
#### `useConditionalBlocker(blockerId, config)`
|
|
343
383
|
|
|
344
384
|
Periodically checks a condition and blocks/unblocks based on the result.
|
|
385
|
+
Use this when polling is an acceptable tradeoff and the condition is not naturally event-driven.
|
|
345
386
|
|
|
346
387
|
**Parameters:**
|
|
347
388
|
|
|
@@ -523,7 +564,9 @@ uiBlockingStoreApi.getState().addBlocker("server-call", {
|
|
|
523
564
|
|
|
524
565
|
## Middleware System
|
|
525
566
|
|
|
526
|
-
|
|
567
|
+
Middleware is optional. Use it when you need analytics, logging, or performance visibility around blocker lifecycle events.
|
|
568
|
+
|
|
569
|
+
Most applications can start without middleware and add it later if blocker observability becomes important.
|
|
527
570
|
|
|
528
571
|
### Middleware Actions
|
|
529
572
|
|
|
@@ -551,6 +594,8 @@ Middleware receives events for the following actions:
|
|
|
551
594
|
|
|
552
595
|
### Built-in Middleware
|
|
553
596
|
|
|
597
|
+
These are extensions around the blocker lifecycle, not the primary onboarding path for the library.
|
|
598
|
+
|
|
554
599
|
#### Analytics Middleware
|
|
555
600
|
|
|
556
601
|
Track blocker events with your analytics provider (Google Analytics, Mixpanel, Amplitude, or custom).
|
|
@@ -645,7 +690,7 @@ configureMiddleware([myCustomMiddleware]);
|
|
|
645
690
|
|
|
646
691
|
### Combining Middleware
|
|
647
692
|
|
|
648
|
-
You can combine multiple middleware
|
|
693
|
+
You can combine multiple middleware when you need broader observability:
|
|
649
694
|
|
|
650
695
|
```jsx
|
|
651
696
|
import {
|
|
@@ -740,6 +785,8 @@ useBlocker("save", { scope: "typo" }); // Type error
|
|
|
740
785
|
|
|
741
786
|
## Use Cases
|
|
742
787
|
|
|
788
|
+
The first four examples below are the most representative starting points.
|
|
789
|
+
|
|
743
790
|
### Loading States
|
|
744
791
|
|
|
745
792
|
```jsx
|
|
@@ -783,7 +830,7 @@ function UserForm() {
|
|
|
783
830
|
}
|
|
784
831
|
```
|
|
785
832
|
|
|
786
|
-
### Unsaved Changes Protection
|
|
833
|
+
### Advanced: Unsaved Changes Protection
|
|
787
834
|
|
|
788
835
|
```jsx
|
|
789
836
|
import { useConfirmableBlocker } from "@okyrychenko-dev/react-action-guard";
|
|
@@ -880,7 +927,7 @@ function MultiStepWizard() {
|
|
|
880
927
|
}
|
|
881
928
|
```
|
|
882
929
|
|
|
883
|
-
### Scheduled Maintenance Window
|
|
930
|
+
### Advanced: Scheduled Maintenance Window
|
|
884
931
|
|
|
885
932
|
```jsx
|
|
886
933
|
import { useScheduledBlocker } from "@okyrychenko-dev/react-action-guard";
|
|
@@ -907,7 +954,7 @@ function App() {
|
|
|
907
954
|
}
|
|
908
955
|
```
|
|
909
956
|
|
|
910
|
-
### Dynamic Blocking Based on State
|
|
957
|
+
### Advanced: Dynamic Blocking Based on State
|
|
911
958
|
|
|
912
959
|
```jsx
|
|
913
960
|
import { useConditionalBlocker } from "@okyrychenko-dev/react-action-guard";
|
package/package.json
CHANGED