@okyrychenko-dev/react-action-guard 1.0.0 → 1.0.2

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 CHANGED
@@ -4,25 +4,18 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@okyrychenko-dev/react-action-guard.svg)](https://www.npmjs.com/package/@okyrychenko-dev/react-action-guard)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- > Elegant UI blocking management for React applications with priorities, scopes, and automatic cleanup
8
-
9
- ## Features
10
-
11
- - Priority-based blocking system
12
- - Scoped blocking (global, specific areas, or multiple scopes)
13
- - Automatic cleanup on unmount
14
- - **Timeout mechanism** - auto-remove blockers after specified time
15
- - **Provider pattern** - isolated stores for SSR, testing, and micro-frontends
16
- - Advanced hooks for different use cases:
17
- - Confirmable blockers with custom dialogs
18
- - Scheduled blocking for maintenance windows
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 { useBlocker, useIsBlocked } from "@okyrychenko-dev/react-action-guard";
44
+ import { useAsyncAction, useIsBlocked } from "@okyrychenko-dev/react-action-guard";
52
45
 
53
- function MyComponent() {
54
- const isBlocked = useIsBlocked("my-scope");
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={isBlocked}>Click me</button>;
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
 
@@ -393,8 +434,6 @@ Provides an isolated store instance for SSR, testing, or micro-frontends. Withou
393
434
  **Props:**
394
435
 
395
436
  - `children: ReactNode` - Child components
396
- - `enableDevtools?: boolean` - Enable Redux DevTools (default: true in development)
397
- - `devtoolsName?: string` - Name for DevTools (default: "UIBlocking")
398
437
  - `middlewares?: Middleware[]` - Initial middlewares to register
399
438
 
400
439
  **Example:**
@@ -421,7 +460,7 @@ function renderWithProvider(ui) {
421
460
  // Micro-frontends - each app has its own blocking state
422
461
  function MicroFrontend() {
423
462
  return (
424
- <UIBlockingProvider devtoolsName="MicroApp-Blocking">
463
+ <UIBlockingProvider>
425
464
  <MicroApp />
426
465
  </UIBlockingProvider>
427
466
  );
@@ -523,7 +562,9 @@ uiBlockingStoreApi.getState().addBlocker("server-call", {
523
562
 
524
563
  ## Middleware System
525
564
 
526
- The library includes a powerful middleware system that allows you to hook into blocker lifecycle events for analytics, logging, and performance monitoring.
565
+ Middleware is optional. Use it when you need analytics, logging, or performance visibility around blocker lifecycle events.
566
+
567
+ Most applications can start without middleware and add it later if blocker observability becomes important.
527
568
 
528
569
  ### Middleware Actions
529
570
 
@@ -551,6 +592,8 @@ Middleware receives events for the following actions:
551
592
 
552
593
  ### Built-in Middleware
553
594
 
595
+ These are extensions around the blocker lifecycle, not the primary onboarding path for the library.
596
+
554
597
  #### Analytics Middleware
555
598
 
556
599
  Track blocker events with your analytics provider (Google Analytics, Mixpanel, Amplitude, or custom).
@@ -645,7 +688,7 @@ configureMiddleware([myCustomMiddleware]);
645
688
 
646
689
  ### Combining Middleware
647
690
 
648
- You can combine multiple middleware for comprehensive monitoring:
691
+ You can combine multiple middleware when you need broader observability:
649
692
 
650
693
  ```jsx
651
694
  import {
@@ -740,6 +783,8 @@ useBlocker("save", { scope: "typo" }); // Type error
740
783
 
741
784
  ## Use Cases
742
785
 
786
+ The first four examples below are the most representative starting points.
787
+
743
788
  ### Loading States
744
789
 
745
790
  ```jsx
@@ -783,7 +828,7 @@ function UserForm() {
783
828
  }
784
829
  ```
785
830
 
786
- ### Unsaved Changes Protection
831
+ ### Advanced: Unsaved Changes Protection
787
832
 
788
833
  ```jsx
789
834
  import { useConfirmableBlocker } from "@okyrychenko-dev/react-action-guard";
@@ -880,7 +925,7 @@ function MultiStepWizard() {
880
925
  }
881
926
  ```
882
927
 
883
- ### Scheduled Maintenance Window
928
+ ### Advanced: Scheduled Maintenance Window
884
929
 
885
930
  ```jsx
886
931
  import { useScheduledBlocker } from "@okyrychenko-dev/react-action-guard";
@@ -907,7 +952,7 @@ function App() {
907
952
  }
908
953
  ```
909
954
 
910
- ### Dynamic Blocking Based on State
955
+ ### Advanced: Dynamic Blocking Based on State
911
956
 
912
957
  ```jsx
913
958
  import { useConditionalBlocker } from "@okyrychenko-dev/react-action-guard";
package/dist/index.cjs CHANGED
@@ -463,8 +463,6 @@ function useUIBlockingStoreFromContext(selector, equalityFn) {
463
463
  }
464
464
  function UIBlockingProvider({
465
465
  children,
466
- enableDevtools = process.env.NODE_ENV === "development",
467
- devtoolsName = "UIBlocking",
468
466
  middlewares = []
469
467
  }) {
470
468
  const handleStoreInit = (store) => {
@@ -472,15 +470,7 @@ function UIBlockingProvider({
472
470
  store.getState().registerMiddleware(`provider-middleware-${index.toString()}`, mw);
473
471
  });
474
472
  };
475
- return /* @__PURE__ */ jsxRuntime.jsx(
476
- BaseUIBlockingProvider,
477
- {
478
- enableDevtools,
479
- devtoolsName,
480
- onStoreInit: handleStoreInit,
481
- children
482
- }
483
- );
473
+ return /* @__PURE__ */ jsxRuntime.jsx(BaseUIBlockingProvider, { onStoreInit: handleStoreInit, children });
484
474
  }
485
475
 
486
476
  // src/store/uiBlockingStore.config.ts