rask-ui 0.18.3 → 0.20.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 +4 -1
- package/dist/component.d.ts +4 -2
- package/dist/component.d.ts.map +1 -1
- package/dist/component.js +37 -47
- package/dist/error.d.ts +3 -14
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +14 -15
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/useAction.d.ts +29 -0
- package/dist/useAction.d.ts.map +1 -0
- package/dist/useAction.js +45 -0
- package/dist/useAsync.d.ts +26 -20
- package/dist/useAsync.d.ts.map +1 -1
- package/dist/useAsync.js +100 -41
- package/dist/useCatchError.d.ts +5 -0
- package/dist/useCatchError.d.ts.map +1 -0
- package/dist/useCatchError.js +16 -0
- package/dist/useSuspend.d.ts +40 -0
- package/dist/useSuspend.d.ts.map +1 -0
- package/dist/useSuspend.js +48 -0
- package/dist/useSuspendAsync.d.ts +18 -0
- package/dist/useSuspendAsync.d.ts.map +1 -0
- package/dist/useSuspendAsync.js +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,7 +48,10 @@ RASK provides a set of reactive hooks for building interactive UIs. These hooks
|
|
|
48
48
|
- **`useState`** - Create reactive state objects
|
|
49
49
|
- **`useEffect`** - Run side effects when dependencies change
|
|
50
50
|
- **`useDerived`** - Derive values from state with automatic caching
|
|
51
|
-
- **`useAsync`** -
|
|
51
|
+
- **`useAsync`** - Fetch async values with automatic observation and cancellation
|
|
52
|
+
- **`useAction`** - Perform async operations with queuing and retry support
|
|
53
|
+
- **`useSuspend`** - Suspend until multiple async values resolve
|
|
54
|
+
- **`useCatchError`** - Catch and handle component errors (hook-based error boundary)
|
|
52
55
|
- **`useRouter`** - Type-safe client-side routing
|
|
53
56
|
- **`createContext`** / **`useContext`** - Share data through the component tree
|
|
54
57
|
- **`useView`** - Compose state and methods into reusable objects
|
package/dist/component.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { VNode, Component, Props, InfernoNode } from "inferno";
|
|
2
|
-
import { Observer } from "./observation";
|
|
2
|
+
import { Observer, Signal } from "./observation";
|
|
3
3
|
export type RaskStatelessFunctionComponent<P extends Props<any>> = (() => VNode) | ((props: P) => VNode);
|
|
4
4
|
export declare class RaskStatelessComponent extends Component {
|
|
5
5
|
renderFn: RaskStatelessFunctionComponent<any>;
|
|
6
6
|
private isNotified;
|
|
7
7
|
private isReconciling;
|
|
8
8
|
observer: Observer;
|
|
9
|
+
propsSignals: Record<string, Signal>;
|
|
9
10
|
private reactiveProps;
|
|
10
11
|
shouldComponentUpdate(): boolean;
|
|
11
12
|
componentWillMount(): void;
|
|
@@ -19,7 +20,8 @@ export type RaskStatefulFunctionComponent<P extends Props<any>> = (() => () => V
|
|
|
19
20
|
export declare class RaskStatefulComponent<P extends Props<any>> extends Component<P> {
|
|
20
21
|
setup: RaskStatefulFunctionComponent<P>;
|
|
21
22
|
private renderFn?;
|
|
22
|
-
|
|
23
|
+
propsSignals: Record<string, Signal>;
|
|
24
|
+
private reactiveProps;
|
|
23
25
|
private isNotified;
|
|
24
26
|
private isReconciling;
|
|
25
27
|
observer: Observer;
|
package/dist/component.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,SAAS,EACT,KAAK,EACL,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAsB,QAAQ,
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,SAAS,EACT,KAAK,EACL,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAsB,QAAQ,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAIrE,MAAM,MAAM,8BAA8B,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAC3D,CAAC,MAAM,KAAK,CAAC,GACb,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC;AAE1B,qBAAa,sBAAuB,SAAQ,SAAS;IAC3C,QAAQ,EAAE,8BAA8B,CAAC,GAAG,CAAC,CAAC;IACtD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAC9B,QAAQ,WAML;IACH,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAC1C,OAAO,CAAC,aAAa,CAAc;IAEnC,qBAAqB,IAAI,OAAO;IAMhC,kBAAkB,IAAI,IAAI;IAG1B,yBAAyB,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI;IAe/C,MAAM;CAOP;AAID,wBAAgB,mBAAmB,2CAElC;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,IAAI,QAM5C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,QAMxC;AAED,MAAM,MAAM,6BAA6B,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAC1D,CAAC,MAAM,MAAM,KAAK,CAAC,GACnB,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,KAAK,CAAC,CAAC;AAEhC,qBAAa,qBAAqB,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IACnE,KAAK,EAAE,6BAA6B,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,CAAC,CAAc;IAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAC1C,OAAO,CAAC,aAAa,CAAc;IAwBnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAE9B,QAAQ,WAOL;IAEH,WAAW,UAAS;IACpB,OAAO,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAM;IAC3D,QAAQ,gBAAa;IACrB,eAAe;IAUf,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IACjC,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IAEnC,iBAAiB,IAAI,IAAI;IAGzB,oBAAoB,IAAI,IAAI;IAI5B,yBAAyB,CACvB,SAAS,EAAE,QAAQ,CAAC;QAAE,QAAQ,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,CAAC,CAAC,GAClD,IAAI;IAcP,qBAAqB,IAAI,OAAO;IAMhC,MAAM;CA6CP;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,SAO9D"}
|
package/dist/component.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createComponentVNode, Component, } from "inferno";
|
|
2
2
|
import { getCurrentObserver, Observer, Signal } from "./observation";
|
|
3
3
|
import { syncBatch } from "./batch";
|
|
4
|
+
import { CatchErrorContext } from "./useCatchError";
|
|
4
5
|
export class RaskStatelessComponent extends Component {
|
|
5
6
|
isNotified = false;
|
|
6
7
|
isReconciling = false;
|
|
@@ -11,6 +12,7 @@ export class RaskStatelessComponent extends Component {
|
|
|
11
12
|
}
|
|
12
13
|
this.forceUpdate();
|
|
13
14
|
});
|
|
15
|
+
propsSignals = {};
|
|
14
16
|
reactiveProps;
|
|
15
17
|
shouldComponentUpdate() {
|
|
16
18
|
const shouldRender = this.isNotified;
|
|
@@ -23,16 +25,17 @@ export class RaskStatelessComponent extends Component {
|
|
|
23
25
|
}
|
|
24
26
|
componentWillReceiveProps(nextProps) {
|
|
25
27
|
this.isReconciling = true;
|
|
28
|
+
const prevProps = this.props;
|
|
29
|
+
this.props = nextProps;
|
|
26
30
|
syncBatch(() => {
|
|
27
|
-
for (const prop in
|
|
28
|
-
if (
|
|
31
|
+
for (const prop in this.propsSignals) {
|
|
32
|
+
if (prevProps[prop] === nextProps[prop]) {
|
|
29
33
|
continue;
|
|
30
34
|
}
|
|
31
|
-
//
|
|
32
|
-
this.
|
|
35
|
+
// This just triggers the signal
|
|
36
|
+
this.propsSignals[prop].notify();
|
|
33
37
|
}
|
|
34
38
|
});
|
|
35
|
-
this.props = nextProps;
|
|
36
39
|
}
|
|
37
40
|
render() {
|
|
38
41
|
const stopObserving = this.observer.observe();
|
|
@@ -59,6 +62,7 @@ export function useCleanup(cb) {
|
|
|
59
62
|
}
|
|
60
63
|
export class RaskStatefulComponent extends Component {
|
|
61
64
|
renderFn;
|
|
65
|
+
propsSignals = {};
|
|
62
66
|
reactiveProps;
|
|
63
67
|
// RECONCILIATION FLAGS
|
|
64
68
|
// --------------------
|
|
@@ -114,16 +118,16 @@ export class RaskStatefulComponent extends Component {
|
|
|
114
118
|
}
|
|
115
119
|
componentWillReceiveProps(nextProps) {
|
|
116
120
|
this.isReconciling = true;
|
|
121
|
+
const prevProps = this.props;
|
|
122
|
+
this.props = nextProps;
|
|
117
123
|
syncBatch(() => {
|
|
118
|
-
for (const prop in
|
|
119
|
-
if (
|
|
124
|
+
for (const prop in this.propsSignals) {
|
|
125
|
+
if (prevProps[prop] === nextProps[prop]) {
|
|
120
126
|
continue;
|
|
121
127
|
}
|
|
122
|
-
|
|
123
|
-
this.reactiveProps[prop] = nextProps[prop];
|
|
128
|
+
this.propsSignals[prop].notify();
|
|
124
129
|
}
|
|
125
130
|
});
|
|
126
|
-
this.props = nextProps;
|
|
127
131
|
}
|
|
128
132
|
shouldComponentUpdate() {
|
|
129
133
|
const shouldRender = this.isNotified;
|
|
@@ -157,10 +161,12 @@ export class RaskStatefulComponent extends Component {
|
|
|
157
161
|
this.isRendering = false;
|
|
158
162
|
}
|
|
159
163
|
catch (error) {
|
|
160
|
-
|
|
164
|
+
const notifyError = this.contexts.get(CatchErrorContext) ||
|
|
165
|
+
this.context.getContext?.(CatchErrorContext);
|
|
166
|
+
if (typeof notifyError !== "function") {
|
|
161
167
|
throw error;
|
|
162
168
|
}
|
|
163
|
-
|
|
169
|
+
notifyError(error);
|
|
164
170
|
}
|
|
165
171
|
finally {
|
|
166
172
|
stopObserving();
|
|
@@ -173,40 +179,24 @@ export function createComponent(props, key) {
|
|
|
173
179
|
return createComponentVNode(4 /* VNodeFlags.ComponentClass */, RaskStatefulComponent, props, key);
|
|
174
180
|
}
|
|
175
181
|
function createReactiveProps(comp) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const observer = getCurrentObserver();
|
|
190
|
-
if (observer) {
|
|
191
|
-
// Lazy create signal only when accessed in reactive context
|
|
192
|
-
let signal = signals.get(prop);
|
|
193
|
-
if (!signal) {
|
|
194
|
-
signal = new Signal();
|
|
195
|
-
signals.set(prop, signal);
|
|
196
|
-
}
|
|
197
|
-
observer.subscribeSignal(signal);
|
|
198
|
-
}
|
|
199
|
-
return comp.props[prop];
|
|
200
|
-
},
|
|
201
|
-
set(value) {
|
|
202
|
-
comp.props[prop] = value;
|
|
203
|
-
// Only notify if signal was created (i.e., prop was accessed reactively)
|
|
204
|
-
const signal = signals.get(prop);
|
|
205
|
-
if (signal) {
|
|
206
|
-
signal.notify();
|
|
182
|
+
const props = new Proxy({}, {
|
|
183
|
+
get(_, prop) {
|
|
184
|
+
// Skip known non-reactive props
|
|
185
|
+
if (prop === "key" || prop === "ref") {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const observer = getCurrentObserver();
|
|
189
|
+
if (observer) {
|
|
190
|
+
// Lazy create signal only when accessed in reactive context
|
|
191
|
+
let signal = comp.propsSignals[prop];
|
|
192
|
+
if (!signal) {
|
|
193
|
+
signal = new Signal();
|
|
194
|
+
comp.propsSignals[prop] = signal;
|
|
207
195
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
196
|
+
observer.subscribeSignal(signal);
|
|
197
|
+
}
|
|
198
|
+
return comp.props[prop];
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
return props;
|
|
212
202
|
}
|
package/dist/error.d.ts
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export declare
|
|
3
|
-
children: any;
|
|
4
|
-
error: (error: unknown) => VNode;
|
|
5
|
-
}, {
|
|
1
|
+
export declare const CatchErrorContext: import("./createContext").Context<(error: unknown) => void>;
|
|
2
|
+
export declare function useCatchError(): {
|
|
6
3
|
error: unknown;
|
|
7
|
-
}
|
|
8
|
-
getChildContext(): {
|
|
9
|
-
notifyError: (error: unknown) => void;
|
|
10
|
-
};
|
|
11
|
-
state: {
|
|
12
|
-
error: null;
|
|
13
|
-
};
|
|
14
|
-
render(): any;
|
|
15
|
-
}
|
|
4
|
+
};
|
|
16
5
|
//# sourceMappingURL=error.d.ts.map
|
package/dist/error.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,iBAAiB,4CAAyB,OAAO,KAAK,IAAI,CAAG,CAAC;AAE3E,wBAAgB,aAAa;WAQK,OAAO;EAOxC"}
|
package/dist/error.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
state = { error: null };
|
|
11
|
-
render() {
|
|
12
|
-
if (this.state.error) {
|
|
13
|
-
return this.props.error(this.state.error);
|
|
14
|
-
}
|
|
15
|
-
return this.props.children;
|
|
1
|
+
import { useState } from "./useState";
|
|
2
|
+
import { createContext, useInjectContext } from "./createContext";
|
|
3
|
+
import { getCurrentComponent } from "./component";
|
|
4
|
+
export const CatchErrorContext = createContext();
|
|
5
|
+
export function useCatchError() {
|
|
6
|
+
const currentComponent = getCurrentComponent();
|
|
7
|
+
if (!currentComponent || currentComponent.isRendering) {
|
|
8
|
+
throw new Error("Only use the useCatchError hook in setup");
|
|
16
9
|
}
|
|
10
|
+
const inject = useInjectContext(CatchErrorContext);
|
|
11
|
+
const state = useState({
|
|
12
|
+
error: null,
|
|
13
|
+
});
|
|
14
|
+
inject((error) => (state.error = error));
|
|
15
|
+
return state;
|
|
17
16
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ export { useCleanup, useMountEffect, RaskStatefulComponent, RaskStatelessCompone
|
|
|
3
3
|
export { createContext, useContext, useInjectContext } from "./createContext";
|
|
4
4
|
export { useState, assignState } from "./useState";
|
|
5
5
|
export { useAsync, Async } from "./useAsync";
|
|
6
|
-
export {
|
|
6
|
+
export { useAction, Action } from "./useAction";
|
|
7
|
+
export { useSuspend } from "./useSuspend";
|
|
8
|
+
export { useCatchError } from "./useCatchError";
|
|
7
9
|
export { useRef } from "./useRef";
|
|
8
10
|
export { useView } from "./useView";
|
|
9
11
|
export { useEffect } from "./useEffect";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EACL,UAAU,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EACL,UAAU,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,cAAc,EACd,SAAS,GACV,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,9 @@ export { useCleanup, useMountEffect, RaskStatefulComponent, RaskStatelessCompone
|
|
|
3
3
|
export { createContext, useContext, useInjectContext } from "./createContext";
|
|
4
4
|
export { useState, assignState } from "./useState";
|
|
5
5
|
export { useAsync } from "./useAsync";
|
|
6
|
-
export {
|
|
6
|
+
export { useAction } from "./useAction";
|
|
7
|
+
export { useSuspend } from "./useSuspend";
|
|
8
|
+
export { useCatchError } from "./useCatchError";
|
|
7
9
|
export { useRef } from "./useRef";
|
|
8
10
|
export { useView } from "./useView";
|
|
9
11
|
export { useEffect } from "./useEffect";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ActionState<T, P> = {
|
|
2
|
+
isPending: false;
|
|
3
|
+
params: null;
|
|
4
|
+
result: null;
|
|
5
|
+
error: null;
|
|
6
|
+
} | {
|
|
7
|
+
isPending: true;
|
|
8
|
+
params: P;
|
|
9
|
+
result: null;
|
|
10
|
+
error: null;
|
|
11
|
+
} | {
|
|
12
|
+
isPending: false;
|
|
13
|
+
params: P;
|
|
14
|
+
result: T;
|
|
15
|
+
error: null;
|
|
16
|
+
} | {
|
|
17
|
+
isPending: false;
|
|
18
|
+
params: P;
|
|
19
|
+
result: null;
|
|
20
|
+
error: Error;
|
|
21
|
+
};
|
|
22
|
+
export type Action<T, P = null> = [
|
|
23
|
+
ActionState<T, P>,
|
|
24
|
+
[
|
|
25
|
+
P
|
|
26
|
+
] extends [null] ? () => void : (params: P) => void
|
|
27
|
+
];
|
|
28
|
+
export declare function useAction<T, P = null>(fn: [P] extends [null] ? () => Promise<T> : (params: P) => Promise<T>): Action<T, P>;
|
|
29
|
+
//# sourceMappingURL=useAction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAction.d.ts","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,IACxB;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,CAAC,CAAC;IACV,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;IACV,MAAM,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;IACV,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;CACd,CAAC;AAEN,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,IAAI;IAChC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IACjB;QAAC,CAAC;KAAC,SAAS,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI;CACtD,CAAC;AAEF,wBAAgB,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,EACnC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACpE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAkDd"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { assignState, useState } from "./useState";
|
|
2
|
+
export function useAction(fn) {
|
|
3
|
+
const state = useState({
|
|
4
|
+
isPending: false,
|
|
5
|
+
error: null,
|
|
6
|
+
params: null,
|
|
7
|
+
result: null,
|
|
8
|
+
});
|
|
9
|
+
let abortController;
|
|
10
|
+
const run = (params) => {
|
|
11
|
+
params = (params || null);
|
|
12
|
+
abortController?.abort();
|
|
13
|
+
const currentAbortController = (abortController = new AbortController());
|
|
14
|
+
assignState(state, {
|
|
15
|
+
isPending: true,
|
|
16
|
+
error: null,
|
|
17
|
+
params,
|
|
18
|
+
result: null,
|
|
19
|
+
});
|
|
20
|
+
fn(params)
|
|
21
|
+
.then((result) => {
|
|
22
|
+
if (currentAbortController.signal.aborted) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
assignState(state, {
|
|
26
|
+
isPending: false,
|
|
27
|
+
error: null,
|
|
28
|
+
params,
|
|
29
|
+
result,
|
|
30
|
+
});
|
|
31
|
+
})
|
|
32
|
+
.catch((error) => {
|
|
33
|
+
if (currentAbortController.signal.aborted) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
assignState(state, {
|
|
37
|
+
isPending: false,
|
|
38
|
+
error,
|
|
39
|
+
params,
|
|
40
|
+
result: null,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
return [state, run];
|
|
45
|
+
}
|
package/dist/useAsync.d.ts
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
|
-
export type AsyncState<
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export type AsyncState<T> = {
|
|
2
|
+
error: Error;
|
|
3
|
+
isLoading: true;
|
|
4
|
+
isRefreshing: false;
|
|
5
|
+
value: null;
|
|
6
|
+
} | {
|
|
7
|
+
error: Error;
|
|
8
|
+
isLoading: false;
|
|
9
|
+
isRefreshing: true;
|
|
10
|
+
value: T;
|
|
6
11
|
} | {
|
|
7
|
-
isPending: true;
|
|
8
|
-
value: T | I;
|
|
9
|
-
params: P;
|
|
10
12
|
error: null;
|
|
13
|
+
isLoading: true;
|
|
14
|
+
isRefreshing: false;
|
|
15
|
+
value: null;
|
|
11
16
|
} | {
|
|
12
|
-
isPending: false;
|
|
13
|
-
params: null;
|
|
14
|
-
value: T;
|
|
15
17
|
error: null;
|
|
18
|
+
isLoading: false;
|
|
19
|
+
isRefreshing: true;
|
|
20
|
+
value: T;
|
|
16
21
|
} | {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
error: null;
|
|
23
|
+
isLoading: false;
|
|
24
|
+
isRefreshing: false;
|
|
25
|
+
value: T;
|
|
21
26
|
};
|
|
22
|
-
export type Async<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export declare function
|
|
27
|
+
export type Async<T extends NonNullable<any>> = [
|
|
28
|
+
AsyncState<T>,
|
|
29
|
+
() => Promise<void>
|
|
30
|
+
];
|
|
31
|
+
export declare function isAsync(value: unknown): boolean;
|
|
32
|
+
export declare function useAsync<T extends NonNullable<any>>(fn: (signal?: AbortSignal) => Promise<T>): Async<T>;
|
|
27
33
|
//# sourceMappingURL=useAsync.d.ts.map
|
package/dist/useAsync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAsync.d.ts","sourceRoot":"","sources":["../src/useAsync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useAsync.d.ts","sourceRoot":"","sources":["../src/useAsync.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,UAAU,CAAC,CAAC,IACpB;IACE,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;IACpB,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,KAAK,EAAE,CAAC,CAAC;CACV,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;IACpB,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,KAAK,EAAE,CAAC,CAAC;CACV,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,KAAK,CAAC;IACpB,KAAK,EAAE,CAAC,CAAC;CACV,CAAC;AAEN,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,WAAW,CAAC,GAAG,CAAC,IAAI;IAC9C,UAAU,CAAC,CAAC,CAAC;IACb,MAAM,OAAO,CAAC,IAAI,CAAC;CACpB,CAAC;AAEF,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,WAWrC;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,WAAW,CAAC,GAAG,CAAC,EACjD,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GA0InC,KAAK,CAAC,CAAC,CAAC,CACd"}
|
package/dist/useAsync.js
CHANGED
|
@@ -1,72 +1,131 @@
|
|
|
1
|
+
import { syncBatch } from "./batch";
|
|
1
2
|
import { useCleanup, getCurrentComponent } from "./component";
|
|
3
|
+
import { Observer } from "./observation";
|
|
2
4
|
import { assignState, useState } from "./useState";
|
|
3
|
-
export function
|
|
5
|
+
export function isAsync(value) {
|
|
6
|
+
if (value === null || typeof value !== "object") {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return Boolean("isLoading" in value &&
|
|
10
|
+
"isRefreshing" in value &&
|
|
11
|
+
"error" in value &&
|
|
12
|
+
"value" in value);
|
|
13
|
+
}
|
|
14
|
+
export function useAsync(fn) {
|
|
4
15
|
const currentComponent = getCurrentComponent();
|
|
5
16
|
if (!currentComponent || currentComponent.isRendering) {
|
|
6
|
-
throw new Error("Only use
|
|
17
|
+
throw new Error("Only use useAsync in component setup");
|
|
7
18
|
}
|
|
8
|
-
const value = args.length === 2 ? args[0] : null;
|
|
9
|
-
const fn = args.length === 2 ? args[1] : args[0];
|
|
10
19
|
const state = useState({
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
isLoading: true,
|
|
21
|
+
isRefreshing: false,
|
|
22
|
+
value: null,
|
|
13
23
|
error: null,
|
|
14
|
-
params: null,
|
|
15
24
|
});
|
|
25
|
+
const refreshResolvers = [];
|
|
16
26
|
let currentAbortController;
|
|
17
|
-
const
|
|
27
|
+
const refresh = () => {
|
|
18
28
|
currentAbortController?.abort();
|
|
19
29
|
const abortController = (currentAbortController = new AbortController());
|
|
20
|
-
const
|
|
30
|
+
const stopObserving = observer.observe();
|
|
31
|
+
const promise = fn(abortController.signal);
|
|
32
|
+
stopObserving();
|
|
21
33
|
promise
|
|
22
34
|
.then((result) => {
|
|
23
35
|
if (abortController.signal.aborted) {
|
|
24
36
|
return;
|
|
25
37
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
syncBatch(() => {
|
|
39
|
+
assignState(state, {
|
|
40
|
+
isLoading: false,
|
|
41
|
+
isRefreshing: false,
|
|
42
|
+
value: result,
|
|
43
|
+
error: null,
|
|
44
|
+
});
|
|
31
45
|
});
|
|
46
|
+
refreshResolvers.forEach((resolver) => resolver.resolve());
|
|
47
|
+
refreshResolvers.length = 0;
|
|
32
48
|
})
|
|
33
49
|
.catch((error) => {
|
|
34
50
|
if (abortController.signal.aborted) {
|
|
35
51
|
return;
|
|
36
52
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
syncBatch(() => {
|
|
54
|
+
assignState(state, {
|
|
55
|
+
isLoading: state.isLoading,
|
|
56
|
+
isRefreshing: state.isRefreshing,
|
|
57
|
+
value: state.value,
|
|
58
|
+
error,
|
|
59
|
+
});
|
|
42
60
|
});
|
|
61
|
+
refreshResolvers.forEach((resolver) => resolver.reject(error));
|
|
62
|
+
refreshResolvers.length = 0;
|
|
43
63
|
});
|
|
44
64
|
return promise;
|
|
45
65
|
};
|
|
46
|
-
|
|
66
|
+
const observer = new Observer(() => {
|
|
67
|
+
syncBatch(() => {
|
|
68
|
+
if (state.isLoading) {
|
|
69
|
+
refresh();
|
|
70
|
+
}
|
|
71
|
+
else if (state.error && state.value === null) {
|
|
72
|
+
assignState(state, {
|
|
73
|
+
isLoading: true,
|
|
74
|
+
isRefreshing: false,
|
|
75
|
+
value: state.value,
|
|
76
|
+
error: null,
|
|
77
|
+
});
|
|
78
|
+
refresh();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
assignState(state, {
|
|
82
|
+
isLoading: false,
|
|
83
|
+
isRefreshing: true,
|
|
84
|
+
value: state.value,
|
|
85
|
+
error: null,
|
|
86
|
+
});
|
|
87
|
+
refresh();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
useCleanup(() => {
|
|
92
|
+
currentAbortController?.abort();
|
|
93
|
+
observer.dispose();
|
|
94
|
+
});
|
|
95
|
+
refresh();
|
|
47
96
|
return [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
97
|
+
state,
|
|
98
|
+
async () => {
|
|
99
|
+
if (state.isLoading && !state.error) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
syncBatch(() => {
|
|
103
|
+
if (state.error && state.value === null) {
|
|
104
|
+
assignState(state, {
|
|
105
|
+
isLoading: true,
|
|
106
|
+
isRefreshing: false,
|
|
107
|
+
value: state.value,
|
|
108
|
+
error: null,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
assignState(state, {
|
|
113
|
+
isLoading: false,
|
|
114
|
+
isRefreshing: true,
|
|
115
|
+
value: state.value,
|
|
116
|
+
error: null,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
let resolve;
|
|
121
|
+
let reject;
|
|
122
|
+
const promise = new Promise((res, rej) => {
|
|
123
|
+
resolve = res;
|
|
124
|
+
reject = rej;
|
|
69
125
|
});
|
|
126
|
+
refreshResolvers.push({ resolve, reject });
|
|
127
|
+
refresh();
|
|
128
|
+
return promise;
|
|
70
129
|
},
|
|
71
130
|
];
|
|
72
131
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCatchError.d.ts","sourceRoot":"","sources":["../src/useCatchError.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,iBAAiB,4CAAyB,OAAO,KAAK,IAAI,CAAG,CAAC;AAE3E,wBAAgB,aAAa;WAQK,OAAO;EAOxC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useState } from "./useState";
|
|
2
|
+
import { createContext, useInjectContext } from "./createContext";
|
|
3
|
+
import { getCurrentComponent } from "./component";
|
|
4
|
+
export const CatchErrorContext = createContext();
|
|
5
|
+
export function useCatchError() {
|
|
6
|
+
const currentComponent = getCurrentComponent();
|
|
7
|
+
if (!currentComponent || currentComponent.isRendering) {
|
|
8
|
+
throw new Error("Only use the useCatchError hook in setup");
|
|
9
|
+
}
|
|
10
|
+
const inject = useInjectContext(CatchErrorContext);
|
|
11
|
+
const state = useState({
|
|
12
|
+
error: null,
|
|
13
|
+
});
|
|
14
|
+
inject((error) => (state.error = error));
|
|
15
|
+
return state;
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AsyncState } from "./useAsync";
|
|
2
|
+
type SuspendState<T extends Record<string, () => any>> = {
|
|
3
|
+
error: Error;
|
|
4
|
+
isLoading: true;
|
|
5
|
+
isRefreshing: false;
|
|
6
|
+
values: {
|
|
7
|
+
[K in keyof T]: ReturnType<T[K]> extends AsyncState<any> ? ReturnType<T[K]>["value"] : ReturnType<T[K]>;
|
|
8
|
+
};
|
|
9
|
+
} | {
|
|
10
|
+
error: Error;
|
|
11
|
+
isLoading: false;
|
|
12
|
+
isRefreshing: true;
|
|
13
|
+
values: {
|
|
14
|
+
[K in keyof T]: ReturnType<T[K]> extends AsyncState<any> ? NonNullable<ReturnType<T[K]>["value"]> : ReturnType<T[K]>;
|
|
15
|
+
};
|
|
16
|
+
} | {
|
|
17
|
+
error: null;
|
|
18
|
+
isLoading: true;
|
|
19
|
+
isRefreshing: false;
|
|
20
|
+
values: {
|
|
21
|
+
[K in keyof T]: ReturnType<T[K]> extends AsyncState<any> ? ReturnType<T[K]>["value"] : ReturnType<T[K]>;
|
|
22
|
+
};
|
|
23
|
+
} | {
|
|
24
|
+
error: null;
|
|
25
|
+
isLoading: false;
|
|
26
|
+
isRefreshing: true;
|
|
27
|
+
values: {
|
|
28
|
+
[K in keyof T]: ReturnType<T[K]> extends AsyncState<any> ? NonNullable<ReturnType<T[K]>["value"]> : ReturnType<T[K]>;
|
|
29
|
+
};
|
|
30
|
+
} | {
|
|
31
|
+
error: null;
|
|
32
|
+
isLoading: false;
|
|
33
|
+
isRefreshing: false;
|
|
34
|
+
values: {
|
|
35
|
+
[K in keyof T]: ReturnType<T[K]> extends AsyncState<any> ? NonNullable<ReturnType<T[K]>["value"]> : ReturnType<T[K]>;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export declare function useSuspend<T extends Record<string, any>>(asyncs: T): SuspendState<T>;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=useSuspend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSuspend.d.ts","sourceRoot":"","sources":["../src/useSuspend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAW,MAAM,YAAY,CAAC;AAIjD,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,IACjD;IACE,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;IACpB,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,GACpD,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GACzB,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;CACH,GACD;IACE,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,GACpD,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GACtC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;CACH,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;IACpB,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,GACpD,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GACzB,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;CACH,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,GACpD,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GACtC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;CACH,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,KAAK,CAAC;IACpB,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,GACpD,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GACtC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;CACH,CAAC;AAEN,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,mBAsDlE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isAsync } from "./useAsync";
|
|
2
|
+
import { useEffect } from "./useEffect";
|
|
3
|
+
import { assignState, useState } from "./useState";
|
|
4
|
+
export function useSuspend(asyncs) {
|
|
5
|
+
const state = useState({
|
|
6
|
+
isLoading: true,
|
|
7
|
+
isRefreshing: false,
|
|
8
|
+
error: null,
|
|
9
|
+
values: Object.keys(asyncs).reduce((aggr, key) => {
|
|
10
|
+
let value = asyncs[key]();
|
|
11
|
+
if (isAsync(value)) {
|
|
12
|
+
value = value.value;
|
|
13
|
+
}
|
|
14
|
+
aggr[key] = value;
|
|
15
|
+
return aggr;
|
|
16
|
+
}, {}),
|
|
17
|
+
});
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
let isLoading = false;
|
|
20
|
+
let isRefreshing = false;
|
|
21
|
+
let error;
|
|
22
|
+
const values = {};
|
|
23
|
+
for (const key in asyncs) {
|
|
24
|
+
let value = asyncs[key]();
|
|
25
|
+
if (!isAsync(value)) {
|
|
26
|
+
values[key] = value;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
values[key] = value.value;
|
|
30
|
+
if (value.isLoading) {
|
|
31
|
+
isLoading = true;
|
|
32
|
+
}
|
|
33
|
+
else if (value.isRefreshing) {
|
|
34
|
+
isRefreshing = true;
|
|
35
|
+
}
|
|
36
|
+
if (value.error) {
|
|
37
|
+
error = value.error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
state.isLoading = isLoading;
|
|
41
|
+
state.isRefreshing = isLoading ? false : isRefreshing;
|
|
42
|
+
state.error = error || null;
|
|
43
|
+
if (!state.isLoading && !state.isRefreshing && !error) {
|
|
44
|
+
assignState(state.values, values);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AsyncState } from "./useAsync";
|
|
2
|
+
type SuspendAsyncState<T extends Record<string, AsyncState<any>>> = {
|
|
3
|
+
values: {
|
|
4
|
+
[K in keyof T]: T[K]["value"];
|
|
5
|
+
};
|
|
6
|
+
} & ({
|
|
7
|
+
isPending: true;
|
|
8
|
+
error: null;
|
|
9
|
+
} | {
|
|
10
|
+
isPending: false;
|
|
11
|
+
error: string;
|
|
12
|
+
} | {
|
|
13
|
+
isPending: false;
|
|
14
|
+
error: null;
|
|
15
|
+
});
|
|
16
|
+
export declare function useSuspendAsync<T extends Record<string, AsyncState<any>>>(asyncs: T): SuspendAsyncState<T>;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=useSuspendAsync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSuspendAsync.d.ts","sourceRoot":"","sources":["../src/useSuspendAsync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAIxC,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI;IAClE,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;KAC9B,CAAC;CACH,GAAG,CACA;IACE,SAAS,EAAE,IAAI,CAAC;IAChB,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,KAAK,EAAE,IAAI,CAAC;CACb,CACJ,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,EACvE,MAAM,EAAE,CAAC,wBAyCV"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useEffect } from "./useEffect";
|
|
2
|
+
import { useState } from "./useState";
|
|
3
|
+
export function useSuspendAsync(asyncs) {
|
|
4
|
+
const state = useState({
|
|
5
|
+
isPending: false,
|
|
6
|
+
error: null,
|
|
7
|
+
values: Object.keys(asyncs).reduce((aggr, key) => ({ ...aggr, [key]: asyncs[key].value }), {}),
|
|
8
|
+
});
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
let isPending = false;
|
|
11
|
+
let error = "";
|
|
12
|
+
for (const key in asyncs) {
|
|
13
|
+
if (asyncs[key].error) {
|
|
14
|
+
error += asyncs[key].error + "\n";
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (asyncs[key].isPending) {
|
|
18
|
+
isPending = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (error) {
|
|
23
|
+
state.isPending = false;
|
|
24
|
+
state.error = error;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (isPending) {
|
|
28
|
+
state.isPending = false;
|
|
29
|
+
state.error = null;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const key in asyncs) {
|
|
33
|
+
state.values[key] = asyncs[key].value;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return state;
|
|
37
|
+
}
|