@t8/react-pending 1.0.24 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,22 +4,36 @@
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@t8/react-pending?labelColor=345&color=46e)](https://www.npmjs.com/package/@t8/react-pending) ![Lightweight](https://img.shields.io/bundlephobia/minzip/@t8/react-pending?label=minzip&labelColor=345&color=46e) ![CSR ✓](https://img.shields.io/badge/CSR-✓-345?labelColor=345) ![SSR ✓](https://img.shields.io/badge/SSR-✓-345?labelColor=345)
6
6
 
7
- Shared or local pending state tracking with a concise API without rewrites in the app's shared state or async actions' internals.
7
+ **Why?** To manage the async action state, whether local or shared, without tightly coupling it with the app state. Decoupled pending state as described here acts like a lightweight scaffolding on top of the action's and component's successful scenario. It's easy to set up from scratch without rewriting the async actions and affecting the app state, and easy to manage further on since it's barely intertwined with other app's internals.
8
8
 
9
+ <!-- docsgen-show-start --
9
10
  ```diff
10
- + let [state, withState] = usePendingState("fetch-items");
11
+ + import { usePendingState } from "@t8/react-pending";
11
12
 
12
- - fetchItems().then(setItems);
13
- + withState(fetchItems()).then(setItems);
13
+ export let ItemList = () => {
14
+ let [items, setItems] = useState([]);
15
+ + let [state, withState] = usePendingState("fetch-items");
14
16
 
15
- + if (!state.complete) return <p>Loading...</p>;
17
+ useEffect(() => {
18
+ - fetchItems().then(setItems);
19
+ + withState(fetchItems()).then(setItems);
20
+ }, [fetchItems, withState]);
21
+
22
+ + if (!state.complete) return <p>Loading...</p>;
23
+ + if (state.error) return <p>An error occurred</p>;
24
+
25
+ return <ul>{items.map(/* ... */)}</ul>;
26
+ };
16
27
  ```
28
+ -- docsgen-show-end -->
17
29
 
18
30
  Installation: `npm i @t8/react-pending`
19
31
 
20
32
  ## Shared pending state
21
33
 
22
- Objective: Track the pending state of the asynchronous action `fetchItems()` to tell the user whether the UI is busy or encountered an error (preferably without rewriting the action and the app's state management). In our setup, there are two components rendering their content with regard to the current state of `fetchItems()`.
34
+ Objective: Track the pending state of the asynchronous action `fetchItems()` to tell the user whether the UI is busy handling the async action or encountered an error, without rewriting the action and the app's state management.
35
+
36
+ In our setup, there are two components rendering their content with regard to the current state of `fetchItems()`, so the pending state is shared between these components:
23
37
 
24
38
  ```diff
25
39
  + import { usePendingState } from "@t8/react-pending";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,42 @@
1
- export * from "./src/PendingState.ts";
2
- export * from "./src/PendingStateContext.ts";
3
- export * from "./src/PendingStateProvider.tsx";
4
- export * from "./src/usePendingState.ts";
1
+ import type { SetStoreState, Store } from "@t8/react-store";
2
+ import type { ReactNode } from "react";
3
+
4
+ export type PendingState = {
5
+ initialized?: boolean | undefined;
6
+ complete?: boolean | undefined;
7
+ time?: number | undefined;
8
+ error?: unknown;
9
+ };
10
+ export declare const PendingStateContext: import("react").Context<Map<string, Store<PendingState>>>;
11
+ export type PendingStateProviderProps = {
12
+ value?: Record<string, PendingState> | Map<string, Store<PendingState>> | null | undefined;
13
+ children?: ReactNode;
14
+ };
15
+ export declare const PendingStateProvider: ({ value, children, }: PendingStateProviderProps) => import("react/jsx-runtime").JSX.Element;
16
+ export type WithStateOptions = {
17
+ silent?: boolean;
18
+ throws?: boolean;
19
+ delay?: number;
20
+ };
21
+ /**
22
+ * Returns an array containing `[state, withState, setState]`:
23
+ * - `state` reflects the state of a value passed to `withState()`;
24
+ * - `withState(value, [options])` enables the tracking of the state
25
+ * of `value`; setting the options to `{silent: true}` prevents
26
+ * `withState()` from updating the state while `value` is pending
27
+ * (e.g. for background or optimistic updates);
28
+ * - `setState()` to directly update the state.
29
+ */
30
+ export declare function usePendingState(
31
+ /**
32
+ * A unique store key or a store. Providing a store key or a
33
+ * shared store allows to share the state across multiple
34
+ * components.
35
+ */
36
+ store?: string | Store<PendingState> | null): [
37
+ PendingState,
38
+ <T>(value: T) => T,
39
+ SetStoreState<PendingState>
40
+ ];
41
+
42
+ export {};
package/dist/index.js CHANGED
@@ -9,6 +9,11 @@ function isStore(x) {
9
9
  return x !== null && typeof x === "object" && "onUpdate" in x && typeof x.onUpdate === "function" && "getState" in x && typeof x.getState === "function" && "setState" in x && typeof x.setState === "function";
10
10
  }
11
11
 
12
+ // node_modules/@t8/store/src/isPersistentStore.ts
13
+ function isPersistentStore(x) {
14
+ return isStore(x) && "sync" in x;
15
+ }
16
+
12
17
  // node_modules/@t8/store/src/Store.ts
13
18
  var Store = class {
14
19
  state;
@@ -38,12 +43,14 @@ var Store = class {
38
43
  // node_modules/@t8/react-store/src/useStore.ts
39
44
  import { useEffect, useMemo, useRef, useState } from "react";
40
45
  function useStore(store, shouldUpdate = true) {
41
- if (!isStore(store)) throw new Error("'store' is not an instance of Store");
46
+ if (!isStore(store))
47
+ throw new Error("'store' is not an instance of Store");
42
48
  let [, setRevision] = useState(-1);
43
49
  let state = store.getState();
44
50
  let setState = useMemo(() => store.setState.bind(store), [store]);
45
51
  let initialStoreRevision = useRef(store.revision);
46
52
  useEffect(() => {
53
+ if (isPersistentStore(store)) store.syncOnce();
47
54
  if (!shouldUpdate) return;
48
55
  let unsubscribe = store.onUpdate((nextState, prevState) => {
49
56
  if (typeof shouldUpdate !== "function" || shouldUpdate(nextState, prevState))
package/package.json CHANGED
@@ -1,47 +1,42 @@
1
- {
2
- "name": "@t8/react-pending",
3
- "version": "1.0.24",
4
- "description": "Concise async action state tracking for React apps",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "type": "module",
8
- "scripts": {
9
- "build": "npx npm-run-all clean compile types",
10
- "clean": "node -e \"require('node:fs').rmSync('dist', { force: true, recursive: true });\"",
11
- "compile": "npx esbuild index.ts --bundle --outdir=dist --platform=neutral --external:react",
12
- "demo": "npx @t8/serve 3000 * tests/async_status -b src/index.tsx",
13
- "prepublishOnly": "npm run build",
14
- "preversion": "npx npm-run-all typecheck shape build test",
15
- "shape": "npx codeshape",
16
- "test": "npx playwright test --project=chromium",
17
- "typecheck": "tsc --noEmit",
18
- "types": "tsc -p tsconfig.build.json"
19
- },
20
- "author": "axtk",
21
- "license": "MIT",
22
- "repository": {
23
- "type": "git",
24
- "url": "git+https://github.com/t8js/react-pending.git"
25
- },
26
- "homepage": "https://t8.js.org/react-pending",
27
- "keywords": [
28
- "async actions",
29
- "pending state",
30
- "react"
31
- ],
32
- "peerDependencies": {
33
- "react": ">=16.8"
34
- },
35
- "devDependencies": {
36
- "@playwright/test": "^1.56.0",
37
- "@t8/serve": "^0.1.35",
38
- "@types/node": "^24.5.2",
39
- "@types/react": "^19.1.10",
40
- "@types/react-dom": "^19.1.9",
41
- "react-dom": "^19.1.1",
42
- "typescript": "^5.9.3"
43
- },
44
- "dependencies": {
45
- "@t8/react-store": "^1.0.35"
46
- }
47
- }
1
+ {
2
+ "name": "@t8/react-pending",
3
+ "version": "1.0.26",
4
+ "description": "Concise async action state tracking for React apps",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "clean": "node -e \"require('node:fs').rmSync('dist', { force: true, recursive: true });\"",
10
+ "compile": "npx esbuild index.ts --bundle --outdir=dist --platform=neutral --external:react",
11
+ "demo": "npx @t8/serve 3000 * tests/async_status -b src/index.tsx",
12
+ "preversion": "npx npm-run-all clean shape compile test",
13
+ "shape": "npx codeshape --typecheck --emit-types",
14
+ "test": "npx playwright test --project=chromium"
15
+ },
16
+ "author": "axtk",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/t8js/react-pending.git"
21
+ },
22
+ "homepage": "https://t8.js.org/react-pending",
23
+ "keywords": [
24
+ "async actions",
25
+ "pending state",
26
+ "react"
27
+ ],
28
+ "peerDependencies": {
29
+ "react": ">=16.8"
30
+ },
31
+ "devDependencies": {
32
+ "@playwright/test": "^1.56.0",
33
+ "@t8/serve": "^0.1.36",
34
+ "@types/node": "^24.10.2",
35
+ "@types/react": "^19.2.7",
36
+ "@types/react-dom": "^19.2.3",
37
+ "react-dom": "^19.2.1"
38
+ },
39
+ "dependencies": {
40
+ "@t8/react-store": "^1.2.4"
41
+ }
42
+ }
@@ -1,6 +0,0 @@
1
- export type PendingState = {
2
- initialized?: boolean | undefined;
3
- complete?: boolean | undefined;
4
- time?: number | undefined;
5
- error?: unknown;
6
- };
@@ -1,3 +0,0 @@
1
- import type { Store } from "@t8/react-store";
2
- import type { PendingState } from "./PendingState.ts";
3
- export declare const PendingStateContext: import("react").Context<Map<string, Store<PendingState>>>;
@@ -1,8 +0,0 @@
1
- import { Store } from "@t8/react-store";
2
- import { type ReactNode } from "react";
3
- import type { PendingState } from "./PendingState.ts";
4
- export type PendingStateProviderProps = {
5
- value?: Record<string, PendingState> | Map<string, Store<PendingState>> | null | undefined;
6
- children?: ReactNode;
7
- };
8
- export declare const PendingStateProvider: ({ value, children, }: PendingStateProviderProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,23 +0,0 @@
1
- import { type SetStoreState, Store } from "@t8/react-store";
2
- import type { PendingState } from "./PendingState.ts";
3
- export type WithStateOptions = {
4
- silent?: boolean;
5
- throws?: boolean;
6
- delay?: number;
7
- };
8
- /**
9
- * Returns an array containing `[state, withState, setState]`:
10
- * - `state` reflects the state of a value passed to `withState()`;
11
- * - `withState(value, [options])` enables the tracking of the state
12
- * of `value`; setting the options to `{silent: true}` prevents
13
- * `withState()` from updating the state while `value` is pending
14
- * (e.g. for background or optimistic updates);
15
- * - `setState()` to directly update the state.
16
- */
17
- export declare function usePendingState(
18
- /**
19
- * A unique store key or a store. Providing a store key or a
20
- * shared store allows to share the state across multiple
21
- * components.
22
- */
23
- store?: string | Store<PendingState> | null): [PendingState, <T>(value: T) => T, SetStoreState<PendingState>];
@@ -1,4 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "include": ["./index.ts", "./src"]
4
- }