@t8/react-pending 1.0.15 → 1.0.16

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.
Files changed (3) hide show
  1. package/README.md +35 -31
  2. package/dist/index.js +3 -5
  3. package/package.json +6 -7
package/README.md CHANGED
@@ -8,46 +8,38 @@ No need to rearrange the app's shared state setup and to rewrite the async actio
8
8
 
9
9
  Installation: `npm i @t8/react-pending`
10
10
 
11
- ## Usage
11
+ ## Example
12
12
 
13
13
  Objective: Track the pending state of the async `fetchItems()` action to tell the user whether the UI is busy or encountered an error (preferably without rewriting the action and the app's state management).
14
14
 
15
15
  ```diff
16
16
  + import { usePendingState } from "@t8/react-pending";
17
17
 
18
- const ItemList = () => {
18
+ export const ItemList = () => {
19
19
  const [items, setItems] = useState([]);
20
- // the custom string key parameter tags the action's state so
21
- // that another component can access this state by the same tag
22
20
  + const [state, withState] = usePendingState("fetch-items");
23
21
 
24
22
  useEffect(() => {
25
- // wrapping fetchItems() to track the async action's state
26
23
  - fetchItems().then(setItems);
27
24
  + withState(fetchItems()).then(setItems);
28
25
  }, [fetchItems, withState]);
29
26
 
30
- + if (!state.complete)
31
- + return <p>Loading...</p>;
32
-
33
- + if (state.error)
34
- + return <p>An error occurred</p>;
27
+ + if (!state.complete) return <p>Loading...</p>;
28
+ + if (state.error) return <p>An error occurred</p>;
35
29
 
36
30
  return <ul>{items.map(/* ... */)}</ul>;
37
31
  };
32
+ ```
38
33
 
39
- const Status = () => {
40
- // reading the "fetch-items" state updated in ItemList
41
- + const [state] = usePendingState("fetch-items");
42
-
43
- if (!state.initialized)
44
- return "Initial";
34
+ ```diff
35
+ + import { usePendingState } from "@t8/react-pending";
45
36
 
46
- if (!state.complete)
47
- return "Busy";
37
+ export const Status = () => {
38
+ + const [state] = usePendingState("fetch-items");
48
39
 
49
- if (state.error)
50
- return "Error";
40
+ if (!state.initialized) return "";
41
+ if (!state.complete) return "Busy";
42
+ if (state.error) return "Error";
51
43
 
52
44
  return "OK";
53
45
  };
@@ -55,37 +47,45 @@ Objective: Track the pending state of the async `fetchItems()` action to tell th
55
47
 
56
48
  [Live demo](https://codesandbox.io/p/sandbox/rrr9cl?file=%2Fsrc%2FItemList.tsx)
57
49
 
58
- 🔹 If the action's state is only used within a single component, it can be used locally by omitting the custom string key parameter of the `usePendingState()` hook.
50
+ 🔹 In this example, the action's value (the `items` array) is stored in the component's local state, but it can be stored in any app state of the developer's choice.
51
+
52
+ ## Shared and local pending state
53
+
54
+ Omit the custom string key parameter of `usePendingState()` to scope the pending state locally within a single component:
59
55
 
60
56
  ```diff
61
- - const [state, withState] = usePendingState("fetch-items");
62
- + const [state, withState] = usePendingState();
57
+ - const [state, withState] = usePendingState("fetch-items"); // shared
58
+ + const [state, withState] = usePendingState(); // local
63
59
  ```
64
60
 
65
- 🔹 In the example above, the action's value (the `items` array) is stored in the component's local state, but it can certainly live in the app's shared state of the developer's choice instead.
66
-
67
- 🔹 Silently tracking the action's pending state, e.g. with background or optimistic updates (preventing `state.complete` from switching to `false` in the pending state):
61
+ ## Silent tracking of background and optimistic updates
68
62
 
69
63
  ```diff
70
64
  - withState(fetchItems())
71
65
  + withState(fetchItems(), { silent: true })
72
66
  ```
73
67
 
74
- 🔹 Revealing the action's pending state after a delay (e.g. to avoid flashing a process indicator when the action is likely to complete by the end of the delay):
68
+ 🔹 This option prevents `state.complete` from switching to `false` in the pending state.
69
+
70
+ ## Delayed pending state
75
71
 
76
72
  ```diff
77
73
  - withState(fetchItems())
78
74
  + withState(fetchItems(), { delay: 500 })
79
75
  ```
80
76
 
81
- 🔹 Allowing the action's Promise value to reject explicitly (e.g. in order to provide the action with a custom rejection handler) along with exposing `state.error` that goes by default:
77
+ 🔹 Use case: avoiding flashing a process indicator when the action is likely to complete by the end of a short delay.
78
+
79
+ ## Custom rejection handler
82
80
 
83
81
  ```diff
84
82
  - withState(fetchItems())
85
83
  + withState(fetchItems(), { throws: true }).catch(handleError)
86
84
  ```
87
85
 
88
- 🔹 Providing an isolated instance of initial shared action state, e.g. for tests or SSR (it can be unnecessary for client-side rendering where the default context value is sufficient, but it can also be used to separate action states of larger self-contained portions of a web app):
86
+ 🔹 This option allows the async action to reject explicitly, along with exposing `state.error` that goes by default.
87
+
88
+ ## Providing blank initial pending state
89
89
 
90
90
  ```diff
91
91
  + import { PendingStateProvider } from "@t8/react-pending";
@@ -96,7 +96,9 @@ Objective: Track the pending state of the async `fetchItems()` action to tell th
96
96
  + </PendingStateProvider>
97
97
  ```
98
98
 
99
- 🔹 Setting a custom initial action state (which is fully optional):
99
+ 🔹 `<PendingStateProvider>` creates an isolated instance of initial shared action state. Prime use cases: tests, SSR. It isn't required with client-side rendering, but it can be used to separate action states of larger self-contained portions of a web app.
100
+
101
+ ## Providing custom initial pending state
100
102
 
101
103
  ```diff
102
104
  + const initialState = {
@@ -109,4 +111,6 @@ Objective: Track the pending state of the async `fetchItems()` action to tell th
109
111
  </PendingStateProvider>
110
112
  ```
111
113
 
112
- With an explicit value or without, the `<PendingStateProvider>`'s nested components will only respond to updates in the particular action states they subscribed to by means of `usePendingState("action-key")`.
114
+ 🔹 While fully optional, this setup allows to override the initial state received from `usePendingState(stateKey)`.
115
+
116
+ 🔹 With an explicit value or without, the `<PendingStateProvider>`'s nested components will only respond to updates in the particular action states they subscribed to by means of `usePendingState(stateKey)`.
package/dist/index.js CHANGED
@@ -12,17 +12,15 @@ function isStore(x) {
12
12
  // node_modules/@t8/store/src/Store.ts
13
13
  var Store = class {
14
14
  state;
15
- callbacks = [];
15
+ callbacks = /* @__PURE__ */ new Set();
16
16
  revision = -1;
17
17
  constructor(data) {
18
18
  this.state = data;
19
19
  }
20
20
  onUpdate(callback) {
21
- this.callbacks.push(callback);
21
+ this.callbacks.add(callback);
22
22
  return () => {
23
- for (let i = this.callbacks.length - 1; i >= 0; i--) {
24
- if (this.callbacks[i] === callback) this.callbacks.splice(i, 1);
25
- }
23
+ this.callbacks.delete(callback);
26
24
  };
27
25
  }
28
26
  getState() {
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "@t8/react-pending",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "Concise async action state tracking for React apps",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "npx npm-run-all clean compile",
9
- "clean": "node -e \"require('node:fs').rmSync('dist', {force: true, recursive: true});\"",
9
+ "clean": "node -e \"require('node:fs').rmSync('dist', { force: true, recursive: true });\"",
10
10
  "compile": "npx esbuild index.ts --bundle --outdir=dist --platform=neutral --external:react",
11
11
  "demo": "npx @t8/serve 3000 * tests/async_status -b src/index.tsx",
12
- "gh-pages": "npx ghstage --theme=t8 --ymid=103784239 --nav=https://raw.githubusercontent.com/t8js/t8js.github.io/refs/heads/main/assets/nav_react.html",
13
- "prepublishOnly": "npx npm-run-all build gh-pages",
12
+ "prepublishOnly": "npm run build",
14
13
  "preversion": "npx npm-run-all typecheck shape build test",
15
14
  "shape": "npx codeshape",
16
15
  "test": "npx playwright test --project=chromium",
@@ -33,14 +32,14 @@
33
32
  },
34
33
  "devDependencies": {
35
34
  "@playwright/test": "^1.55.1",
36
- "@t8/serve": "^0.1.19",
35
+ "@t8/serve": "^0.1.29",
37
36
  "@types/node": "^24.5.2",
38
37
  "@types/react": "^19.1.10",
39
38
  "@types/react-dom": "^19.1.9",
40
39
  "react-dom": "^19.1.1",
41
- "typescript": "^5.9.2"
40
+ "typescript": "^5.9.3"
42
41
  },
43
42
  "dependencies": {
44
- "@t8/react-store": "^1.0.21"
43
+ "@t8/react-store": "^1.0.23"
45
44
  }
46
45
  }