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 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`** - Manage async operations (fetch, mutations, polling, etc.)
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
@@ -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
- private reactiveProps?;
23
+ propsSignals: Record<string, Signal>;
24
+ private reactiveProps;
23
25
  private isNotified;
24
26
  private isReconciling;
25
27
  observer: Observer;
@@ -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,EAAU,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,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,OAAO,CAAC,aAAa,CAAC,CAAa;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;CAyCP;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,SAO9D"}
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 nextProps) {
28
- if (this.props[prop] === nextProps[prop]) {
31
+ for (const prop in this.propsSignals) {
32
+ if (prevProps[prop] === nextProps[prop]) {
29
33
  continue;
30
34
  }
31
- // @ts-ignore
32
- this.reactiveProps[prop] = nextProps[prop];
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 nextProps) {
119
- if (this.props[prop] === nextProps[prop]) {
124
+ for (const prop in this.propsSignals) {
125
+ if (prevProps[prop] === nextProps[prop]) {
120
126
  continue;
121
127
  }
122
- // @ts-ignore
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
- if (typeof this.context.notifyError !== "function") {
164
+ const notifyError = this.contexts.get(CatchErrorContext) ||
165
+ this.context.getContext?.(CatchErrorContext);
166
+ if (typeof notifyError !== "function") {
161
167
  throw error;
162
168
  }
163
- this.context.notifyError(error);
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 reactiveProps = {};
177
- const signals = new Map();
178
- for (const prop in comp.props) {
179
- const value = comp.props[prop];
180
- // Skip known non-reactive props
181
- if (prop === "key" || prop === "ref") {
182
- reactiveProps[prop] = value;
183
- continue;
184
- }
185
- // Only create reactive getters for primitives
186
- Object.defineProperty(reactiveProps, prop, {
187
- enumerable: true,
188
- get() {
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
- return reactiveProps;
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
- import { Component, VNode } from "inferno";
2
- export declare class ErrorBoundary extends Component<{
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
@@ -1 +1 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAE3C,qBAAa,aAAc,SAAQ,SAAS,CAC1C;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAA;CAAE,EACnD;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CACnB;IACC,eAAe;6BAEU,OAAO;;IAKhC,KAAK;;MAAmB;IAExB,MAAM;CAOP"}
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 { Component } from "inferno";
2
- export class ErrorBoundary extends Component {
3
- getChildContext() {
4
- return {
5
- notifyError: (error) => {
6
- this.setState({ error });
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 { ErrorBoundary } from "./error";
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";
@@ -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,SAAS,CAAC;AACxC,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"}
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 { ErrorBoundary } from "./error";
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
+ }
@@ -1,27 +1,33 @@
1
- export type AsyncState<P, T, I = null> = {
2
- isPending: false;
3
- params: null;
4
- value: I;
5
- error: null;
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
- isPending: false;
18
- params: null;
19
- value: T | I;
20
- error: string;
22
+ error: null;
23
+ isLoading: false;
24
+ isRefreshing: false;
25
+ value: T;
21
26
  };
22
- export type Async<A, B = never, I = null> = [B] extends [never] ? [AsyncState<null, A, I>, () => void] : [AsyncState<A, B, I>, (params: A) => void];
23
- export declare function useAsync<T>(initialValue: T, fn: (params: undefined, signal: AbortSignal) => Promise<T>): Async<T, never, T>;
24
- export declare function useAsync<P, T>(initialValue: T, fn: (params: P, signal: AbortSignal) => Promise<T>): Async<P, T, T>;
25
- export declare function useAsync<T>(fn: (params: undefined, signal: AbortSignal) => Promise<T>): Async<T>;
26
- export declare function useAsync<P, T>(fn: (params: P, signal: AbortSignal) => Promise<T>): Async<P, T>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"useAsync.d.ts","sourceRoot":"","sources":["../src/useAsync.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,IACjC;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,IAAI,CAAC;IAChB,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,MAAM,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,MAAM,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC3D,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,GACpC,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC;AAE/C,wBAAgB,QAAQ,CAAC,CAAC,EACxB,YAAY,EAAE,CAAC,EACf,EAAE,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GACzD,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACtB,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,YAAY,EAAE,CAAC,EACf,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GACjD,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAClB,wBAAgB,QAAQ,CAAC,CAAC,EACxB,EAAE,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GACzD,KAAK,CAAC,CAAC,CAAC,CAAC;AACZ,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GACjD,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC"}
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 useAsync(...args) {
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 useTask in component setup");
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
- isPending: false,
12
- value,
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 fetch = (params) => {
27
+ const refresh = () => {
18
28
  currentAbortController?.abort();
19
29
  const abortController = (currentAbortController = new AbortController());
20
- const promise = fn(params, abortController.signal);
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
- assignState(state, {
27
- isPending: false,
28
- value: result,
29
- error: null,
30
- params: null,
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
- assignState(state, {
38
- isPending: false,
39
- value: state.value,
40
- error: String(error),
41
- params: null,
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
- useCleanup(() => currentAbortController?.abort());
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
- get isPending() {
50
- return state.isPending;
51
- },
52
- get value() {
53
- return state.value;
54
- },
55
- get error() {
56
- return state.error;
57
- },
58
- get params() {
59
- return state.params;
60
- },
61
- },
62
- (params) => {
63
- fetch(params);
64
- assignState(state, {
65
- isPending: true,
66
- value: state.value,
67
- error: null,
68
- params: (params || null),
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,5 @@
1
+ export declare const CatchErrorContext: import("./createContext").Context<(error: unknown) => void>;
2
+ export declare function useCatchError(): {
3
+ error: unknown;
4
+ };
5
+ //# sourceMappingURL=useCatchError.d.ts.map
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rask-ui",
3
- "version": "0.18.3",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",