@t8/react-pending 1.0.32 → 1.1.0
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 +27 -27
- package/dist/index.cjs +45 -45
- package/dist/index.d.ts +11 -9
- package/dist/index.mjs +45 -45
- package/package.json +4 -3
- package/src/usePendingState.ts +17 -11
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# T8 React Pending
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Self-contained async action state management for React apps
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@t8/react-pending) 
|
|
5
|
+
[](https://www.npmjs.com/package/@t8/react-pending) 
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Features:** Decoupled from app state management and async actions' internals · Local or shared async action state · CSR/SSR-compatible
|
|
8
8
|
|
|
9
9
|
<!-- docsgen-show-start --
|
|
10
10
|
```diff
|
|
@@ -12,15 +12,15 @@ A concise async action state management lib for React apps
|
|
|
12
12
|
|
|
13
13
|
export let ItemList = () => {
|
|
14
14
|
let [items, setItems] = useState([]);
|
|
15
|
-
+ let
|
|
15
|
+
+ let { complete, error, track } = usePendingState("fetch-items");
|
|
16
16
|
|
|
17
17
|
useEffect(() => {
|
|
18
18
|
- fetchItems().then(setItems);
|
|
19
|
-
+
|
|
20
|
-
}, [fetchItems,
|
|
19
|
+
+ track(fetchItems()).then(setItems);
|
|
20
|
+
}, [fetchItems, track]);
|
|
21
21
|
|
|
22
|
-
+ if (!
|
|
23
|
-
+ if (
|
|
22
|
+
+ if (!complete) return <p>Loading...</p>;
|
|
23
|
+
+ if (error) return <p>An error occurred</p>;
|
|
24
24
|
|
|
25
25
|
return <ul>{items.map(/* ... */)}</ul>;
|
|
26
26
|
};
|
|
@@ -40,15 +40,15 @@ In our setup, there are two components rendering their content with regard to th
|
|
|
40
40
|
|
|
41
41
|
export let ItemList = () => {
|
|
42
42
|
let [items, setItems] = useState([]);
|
|
43
|
-
+ let
|
|
43
|
+
+ let { complete, error, track } = usePendingState("fetch-items");
|
|
44
44
|
|
|
45
45
|
useEffect(() => {
|
|
46
46
|
- fetchItems().then(setItems);
|
|
47
|
-
+
|
|
48
|
-
}, [fetchItems,
|
|
47
|
+
+ track(fetchItems()).then(setItems);
|
|
48
|
+
}, [fetchItems, track]);
|
|
49
49
|
|
|
50
|
-
+ if (!
|
|
51
|
-
+ if (
|
|
50
|
+
+ if (!complete) return <p>Loading...</p>;
|
|
51
|
+
+ if (error) return <p>An error occurred</p>;
|
|
52
52
|
|
|
53
53
|
return <ul>{items.map(/* ... */)}</ul>;
|
|
54
54
|
};
|
|
@@ -58,11 +58,11 @@ In our setup, there are two components rendering their content with regard to th
|
|
|
58
58
|
+ import { usePendingState } from "@t8/react-pending";
|
|
59
59
|
|
|
60
60
|
export let Status = () => {
|
|
61
|
-
+ let
|
|
61
|
+
+ let { initialized, complete, error } = usePendingState("fetch-items");
|
|
62
62
|
|
|
63
|
-
if (!
|
|
64
|
-
if (!
|
|
65
|
-
if (
|
|
63
|
+
if (!initialized) return null;
|
|
64
|
+
if (!complete) return <>Busy</>;
|
|
65
|
+
if (error) return <>Error</>;
|
|
66
66
|
|
|
67
67
|
return <>OK</>;
|
|
68
68
|
};
|
|
@@ -79,24 +79,24 @@ In our setup, there are two components rendering their content with regard to th
|
|
|
79
79
|
Omit the custom string key parameter of `usePendingState()` to scope the pending state locally within a single component:
|
|
80
80
|
|
|
81
81
|
```diff
|
|
82
|
-
- let
|
|
83
|
-
+ let
|
|
82
|
+
- let { complete } = usePendingState("fetch-items"); // shared
|
|
83
|
+
+ let { complete } = usePendingState(); // local
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
## Silent tracking of background actions and optimistic updates
|
|
87
87
|
|
|
88
88
|
```diff
|
|
89
|
-
-
|
|
90
|
-
+
|
|
89
|
+
- track(fetchItems())
|
|
90
|
+
+ track(fetchItems(), { silent: true })
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
⬥ This option prevents `
|
|
93
|
+
⬥ This option prevents `complete` from switching to `false` in the pending state.
|
|
94
94
|
|
|
95
95
|
## Delayed pending state
|
|
96
96
|
|
|
97
97
|
```diff
|
|
98
|
-
-
|
|
99
|
-
+
|
|
98
|
+
- track(fetchItems())
|
|
99
|
+
+ track(fetchItems(), { delay: 500 }) // in milliseconds
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
⬥ Use case: avoiding flashing a process indicator when the action is likely to complete by the end of a short delay.
|
|
@@ -104,11 +104,11 @@ Omit the custom string key parameter of `usePendingState()` to scope the pending
|
|
|
104
104
|
## Custom rejection handler
|
|
105
105
|
|
|
106
106
|
```diff
|
|
107
|
-
-
|
|
108
|
-
+
|
|
107
|
+
- track(fetchItems())
|
|
108
|
+
+ track(fetchItems(), { throws: true }).catch(handleError)
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
⬥ This option allows the async action to reject explicitly, along with exposing `
|
|
111
|
+
⬥ This option allows the async action to reject explicitly, along with exposing `error` returned from `usePendingState()` that goes by default.
|
|
112
112
|
|
|
113
113
|
## Providing blank initial pending state
|
|
114
114
|
|
package/dist/index.cjs
CHANGED
|
@@ -33,12 +33,11 @@ function createState(initialized = false, complete = false, error) {
|
|
|
33
33
|
* key or a shared store allows to share the state across multiple
|
|
34
34
|
* components.
|
|
35
35
|
*
|
|
36
|
-
* @returns `
|
|
37
|
-
* - `
|
|
38
|
-
* - `
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* directly with an another state value.
|
|
36
|
+
* @returns `{ initialized, complete, error, track, update }`, where
|
|
37
|
+
* - `initialized`, `complete`, `error` reflect the current action's state;
|
|
38
|
+
* - `track(action, options?)` tracks the `actions`'s state;
|
|
39
|
+
* - `update(nextState | ((state) => nextState))` can be used to replace
|
|
40
|
+
* the current `state` value directly with an another state value.
|
|
42
41
|
*/
|
|
43
42
|
function usePendingState(store) {
|
|
44
43
|
let storeMap = (0, react.useContext)(PendingStateContext);
|
|
@@ -62,49 +61,50 @@ function usePendingState(store) {
|
|
|
62
61
|
storeMap,
|
|
63
62
|
storeItemInited
|
|
64
63
|
]));
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
let
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}));
|
|
76
|
-
else delayedPending = setTimeout(() => {
|
|
77
|
-
setState((prevState) => ({
|
|
78
|
-
...prevState,
|
|
79
|
-
...createState(true, false)
|
|
80
|
-
}));
|
|
81
|
-
delayedPending = null;
|
|
82
|
-
}, delay);
|
|
83
|
-
}
|
|
84
|
-
return value.then((resolvedValue) => {
|
|
85
|
-
if (delayedPending !== null) clearTimeout(delayedPending);
|
|
86
|
-
setState((prevState) => ({
|
|
87
|
-
...prevState,
|
|
88
|
-
...createState(true, true)
|
|
89
|
-
}));
|
|
90
|
-
return resolvedValue;
|
|
91
|
-
}).catch((error) => {
|
|
92
|
-
if (delayedPending !== null) clearTimeout(delayedPending);
|
|
64
|
+
let track = (0, react.useCallback)((value, options) => {
|
|
65
|
+
if (value instanceof Promise) {
|
|
66
|
+
let delayedPending = null;
|
|
67
|
+
if (!options?.silent) {
|
|
68
|
+
let delay = options?.delay;
|
|
69
|
+
if (delay === void 0) setState((prevState) => ({
|
|
70
|
+
...prevState,
|
|
71
|
+
...createState(true, false)
|
|
72
|
+
}));
|
|
73
|
+
else delayedPending = setTimeout(() => {
|
|
93
74
|
setState((prevState) => ({
|
|
94
75
|
...prevState,
|
|
95
|
-
...createState(true,
|
|
76
|
+
...createState(true, false)
|
|
96
77
|
}));
|
|
97
|
-
|
|
98
|
-
});
|
|
78
|
+
delayedPending = null;
|
|
79
|
+
}, delay);
|
|
99
80
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
81
|
+
return value.then((resolvedValue) => {
|
|
82
|
+
if (delayedPending !== null) clearTimeout(delayedPending);
|
|
83
|
+
setState((prevState) => ({
|
|
84
|
+
...prevState,
|
|
85
|
+
...createState(true, true)
|
|
86
|
+
}));
|
|
87
|
+
return resolvedValue;
|
|
88
|
+
}).catch((error) => {
|
|
89
|
+
if (delayedPending !== null) clearTimeout(delayedPending);
|
|
90
|
+
setState((prevState) => ({
|
|
91
|
+
...prevState,
|
|
92
|
+
...createState(true, true, error)
|
|
93
|
+
}));
|
|
94
|
+
if (options?.throws) throw error;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
setState((prevState) => ({
|
|
98
|
+
...prevState,
|
|
99
|
+
...createState(true, true)
|
|
100
|
+
}));
|
|
101
|
+
return value;
|
|
102
|
+
}, [setState]);
|
|
103
|
+
return {
|
|
104
|
+
...state,
|
|
105
|
+
track,
|
|
106
|
+
update: setState
|
|
107
|
+
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
exports.PendingStateContext = PendingStateContext;
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ declare const PendingStateProvider: ({
|
|
|
21
21
|
children
|
|
22
22
|
}: PendingStateProviderProps) => react_jsx_runtime0.JSX.Element;
|
|
23
23
|
|
|
24
|
-
type
|
|
24
|
+
type TrackOptions = {
|
|
25
25
|
/**
|
|
26
26
|
* Whether to track the action state silently (e.g. with a background
|
|
27
27
|
* action or an optimistic update).
|
|
@@ -51,13 +51,15 @@ type WithStateOptions = {
|
|
|
51
51
|
* key or a shared store allows to share the state across multiple
|
|
52
52
|
* components.
|
|
53
53
|
*
|
|
54
|
-
* @returns `
|
|
55
|
-
* - `
|
|
56
|
-
* - `
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* directly with an another state value.
|
|
54
|
+
* @returns `{ initialized, complete, error, track, update }`, where
|
|
55
|
+
* - `initialized`, `complete`, `error` reflect the current action's state;
|
|
56
|
+
* - `track(action, options?)` tracks the `actions`'s state;
|
|
57
|
+
* - `update(nextState | ((state) => nextState))` can be used to replace
|
|
58
|
+
* the current `state` value directly with an another state value.
|
|
60
59
|
*/
|
|
61
|
-
declare function usePendingState(store?: string | Store<PendingState> | null):
|
|
60
|
+
declare function usePendingState(store?: string | Store<PendingState> | null): PendingState & {
|
|
61
|
+
track: <T>(value: T) => T;
|
|
62
|
+
update: SetStoreValue<PendingState>;
|
|
63
|
+
};
|
|
62
64
|
|
|
63
|
-
export { PendingState, PendingStateContext, PendingStateProvider, PendingStateProviderProps,
|
|
65
|
+
export { PendingState, PendingStateContext, PendingStateProvider, PendingStateProviderProps, TrackOptions, usePendingState };
|
package/dist/index.mjs
CHANGED
|
@@ -33,12 +33,11 @@ function createState(initialized = false, complete = false, error) {
|
|
|
33
33
|
* key or a shared store allows to share the state across multiple
|
|
34
34
|
* components.
|
|
35
35
|
*
|
|
36
|
-
* @returns `
|
|
37
|
-
* - `
|
|
38
|
-
* - `
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* directly with an another state value.
|
|
36
|
+
* @returns `{ initialized, complete, error, track, update }`, where
|
|
37
|
+
* - `initialized`, `complete`, `error` reflect the current action's state;
|
|
38
|
+
* - `track(action, options?)` tracks the `actions`'s state;
|
|
39
|
+
* - `update(nextState | ((state) => nextState))` can be used to replace
|
|
40
|
+
* the current `state` value directly with an another state value.
|
|
42
41
|
*/
|
|
43
42
|
function usePendingState(store) {
|
|
44
43
|
let storeMap = useContext(PendingStateContext);
|
|
@@ -62,49 +61,50 @@ function usePendingState(store) {
|
|
|
62
61
|
storeMap,
|
|
63
62
|
storeItemInited
|
|
64
63
|
]));
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
let
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}));
|
|
76
|
-
else delayedPending = setTimeout(() => {
|
|
77
|
-
setState((prevState) => ({
|
|
78
|
-
...prevState,
|
|
79
|
-
...createState(true, false)
|
|
80
|
-
}));
|
|
81
|
-
delayedPending = null;
|
|
82
|
-
}, delay);
|
|
83
|
-
}
|
|
84
|
-
return value.then((resolvedValue) => {
|
|
85
|
-
if (delayedPending !== null) clearTimeout(delayedPending);
|
|
86
|
-
setState((prevState) => ({
|
|
87
|
-
...prevState,
|
|
88
|
-
...createState(true, true)
|
|
89
|
-
}));
|
|
90
|
-
return resolvedValue;
|
|
91
|
-
}).catch((error) => {
|
|
92
|
-
if (delayedPending !== null) clearTimeout(delayedPending);
|
|
64
|
+
let track = useCallback((value, options) => {
|
|
65
|
+
if (value instanceof Promise) {
|
|
66
|
+
let delayedPending = null;
|
|
67
|
+
if (!options?.silent) {
|
|
68
|
+
let delay = options?.delay;
|
|
69
|
+
if (delay === void 0) setState((prevState) => ({
|
|
70
|
+
...prevState,
|
|
71
|
+
...createState(true, false)
|
|
72
|
+
}));
|
|
73
|
+
else delayedPending = setTimeout(() => {
|
|
93
74
|
setState((prevState) => ({
|
|
94
75
|
...prevState,
|
|
95
|
-
...createState(true,
|
|
76
|
+
...createState(true, false)
|
|
96
77
|
}));
|
|
97
|
-
|
|
98
|
-
});
|
|
78
|
+
delayedPending = null;
|
|
79
|
+
}, delay);
|
|
99
80
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
81
|
+
return value.then((resolvedValue) => {
|
|
82
|
+
if (delayedPending !== null) clearTimeout(delayedPending);
|
|
83
|
+
setState((prevState) => ({
|
|
84
|
+
...prevState,
|
|
85
|
+
...createState(true, true)
|
|
86
|
+
}));
|
|
87
|
+
return resolvedValue;
|
|
88
|
+
}).catch((error) => {
|
|
89
|
+
if (delayedPending !== null) clearTimeout(delayedPending);
|
|
90
|
+
setState((prevState) => ({
|
|
91
|
+
...prevState,
|
|
92
|
+
...createState(true, true, error)
|
|
93
|
+
}));
|
|
94
|
+
if (options?.throws) throw error;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
setState((prevState) => ({
|
|
98
|
+
...prevState,
|
|
99
|
+
...createState(true, true)
|
|
100
|
+
}));
|
|
101
|
+
return value;
|
|
102
|
+
}, [setState]);
|
|
103
|
+
return {
|
|
104
|
+
...state,
|
|
105
|
+
track,
|
|
106
|
+
update: setState
|
|
107
|
+
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
export { PendingStateContext, PendingStateProvider, usePendingState };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t8/react-pending",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Self-contained async action state management for React apps",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"keywords": [
|
|
23
23
|
"async actions",
|
|
24
24
|
"pending state",
|
|
25
|
+
"async state",
|
|
25
26
|
"react"
|
|
26
27
|
],
|
|
27
28
|
"peerDependencies": {
|
|
@@ -36,6 +37,6 @@
|
|
|
36
37
|
"react-dom": "^19.2.3"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@t8/react-store": "^1.2.
|
|
40
|
+
"@t8/react-store": "^1.2.8"
|
|
40
41
|
}
|
|
41
42
|
}
|
package/src/usePendingState.ts
CHANGED
|
@@ -16,7 +16,7 @@ function createState(
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export type
|
|
19
|
+
export type TrackOptions = {
|
|
20
20
|
/**
|
|
21
21
|
* Whether to track the action state silently (e.g. with a background
|
|
22
22
|
* action or an optimistic update).
|
|
@@ -47,16 +47,18 @@ export type WithStateOptions = {
|
|
|
47
47
|
* key or a shared store allows to share the state across multiple
|
|
48
48
|
* components.
|
|
49
49
|
*
|
|
50
|
-
* @returns `
|
|
51
|
-
* - `
|
|
52
|
-
* - `
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* directly with an another state value.
|
|
50
|
+
* @returns `{ initialized, complete, error, track, update }`, where
|
|
51
|
+
* - `initialized`, `complete`, `error` reflect the current action's state;
|
|
52
|
+
* - `track(action, options?)` tracks the `actions`'s state;
|
|
53
|
+
* - `update(nextState | ((state) => nextState))` can be used to replace
|
|
54
|
+
* the current `state` value directly with an another state value.
|
|
56
55
|
*/
|
|
57
56
|
export function usePendingState(
|
|
58
57
|
store?: string | Store<PendingState> | null,
|
|
59
|
-
):
|
|
58
|
+
): PendingState & {
|
|
59
|
+
track: <T>(value: T) => T;
|
|
60
|
+
update: SetStoreValue<PendingState>;
|
|
61
|
+
} {
|
|
60
62
|
let storeMap = useContext(PendingStateContext);
|
|
61
63
|
let storeRef = useRef<Store<PendingState> | null>(null);
|
|
62
64
|
let [storeItemInited, setStoreItemInited] = useState(false);
|
|
@@ -84,8 +86,8 @@ export function usePendingState(
|
|
|
84
86
|
|
|
85
87
|
let [state, setState] = useStore(resolvedStore);
|
|
86
88
|
|
|
87
|
-
let
|
|
88
|
-
<T>(value: T, options?:
|
|
89
|
+
let track = useCallback(
|
|
90
|
+
<T>(value: T, options?: TrackOptions): T => {
|
|
89
91
|
if (value instanceof Promise) {
|
|
90
92
|
let delayedPending: ReturnType<typeof setTimeout> | null = null;
|
|
91
93
|
|
|
@@ -141,5 +143,9 @@ export function usePendingState(
|
|
|
141
143
|
[setState],
|
|
142
144
|
);
|
|
143
145
|
|
|
144
|
-
return
|
|
146
|
+
return {
|
|
147
|
+
...state,
|
|
148
|
+
track,
|
|
149
|
+
update: setState,
|
|
150
|
+
};
|
|
145
151
|
}
|