@j13b/react-state 0.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.
Files changed (91) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +16 -0
  3. package/README.md +60 -0
  4. package/dist/cjs/hooks/use_runner_error.d.ts +25 -0
  5. package/dist/cjs/hooks/use_runner_error.js +30 -0
  6. package/dist/cjs/hooks/use_runner_error_effect.d.ts +28 -0
  7. package/dist/cjs/hooks/use_runner_error_effect.js +35 -0
  8. package/dist/cjs/hooks/use_runner_feedback.d.ts +26 -0
  9. package/dist/cjs/hooks/use_runner_feedback.js +31 -0
  10. package/dist/cjs/hooks/use_runner_feedback_effect.d.ts +30 -0
  11. package/dist/cjs/hooks/use_runner_feedback_effect.js +37 -0
  12. package/dist/cjs/hooks/use_runner_progress.d.ts +21 -0
  13. package/dist/cjs/hooks/use_runner_progress.js +7 -0
  14. package/dist/cjs/hooks/use_runner_progress_effect.d.ts +31 -0
  15. package/dist/cjs/hooks/use_runner_progress_effect.js +38 -0
  16. package/dist/cjs/hooks/use_runner_status.d.ts +23 -0
  17. package/dist/cjs/hooks/use_runner_status.js +28 -0
  18. package/dist/cjs/hooks/use_runner_status_effect.d.ts +24 -0
  19. package/dist/cjs/hooks/use_runner_status_effect.js +31 -0
  20. package/dist/cjs/hooks/use_signal_value.d.ts +38 -0
  21. package/dist/cjs/hooks/use_signal_value.js +56 -0
  22. package/dist/cjs/hooks/use_signal_value_effect.d.ts +34 -0
  23. package/dist/cjs/hooks/use_signal_value_effect.js +60 -0
  24. package/dist/cjs/hooks/use_update.d.ts +14 -0
  25. package/dist/cjs/hooks/use_update.js +23 -0
  26. package/dist/cjs/index.d.ts +21 -0
  27. package/dist/cjs/index.js +37 -0
  28. package/dist/cjs/package.json +3 -0
  29. package/dist/hooks/use_runner_error.d.ts +26 -0
  30. package/dist/hooks/use_runner_error.d.ts.map +1 -0
  31. package/dist/hooks/use_runner_error.js +32 -0
  32. package/dist/hooks/use_runner_error.js.map +1 -0
  33. package/dist/hooks/use_runner_error_effect.d.ts +29 -0
  34. package/dist/hooks/use_runner_error_effect.d.ts.map +1 -0
  35. package/dist/hooks/use_runner_error_effect.js +37 -0
  36. package/dist/hooks/use_runner_error_effect.js.map +1 -0
  37. package/dist/hooks/use_runner_feedback.d.ts +21 -0
  38. package/dist/hooks/use_runner_feedback.d.ts.map +1 -0
  39. package/dist/hooks/use_runner_feedback.js +27 -0
  40. package/dist/hooks/use_runner_feedback.js.map +1 -0
  41. package/dist/hooks/use_runner_feedback_effect.d.ts +25 -0
  42. package/dist/hooks/use_runner_feedback_effect.d.ts.map +1 -0
  43. package/dist/hooks/use_runner_feedback_effect.js +33 -0
  44. package/dist/hooks/use_runner_feedback_effect.js.map +1 -0
  45. package/dist/hooks/use_runner_progress.d.ts +3 -0
  46. package/dist/hooks/use_runner_progress.d.ts.map +1 -0
  47. package/dist/hooks/use_runner_progress.js +9 -0
  48. package/dist/hooks/use_runner_progress.js.map +1 -0
  49. package/dist/hooks/use_runner_progress_effect.d.ts +26 -0
  50. package/dist/hooks/use_runner_progress_effect.d.ts.map +1 -0
  51. package/dist/hooks/use_runner_progress_effect.js +34 -0
  52. package/dist/hooks/use_runner_progress_effect.js.map +1 -0
  53. package/dist/hooks/use_runner_status.d.ts +24 -0
  54. package/dist/hooks/use_runner_status.d.ts.map +1 -0
  55. package/dist/hooks/use_runner_status.js +30 -0
  56. package/dist/hooks/use_runner_status.js.map +1 -0
  57. package/dist/hooks/use_runner_status_effect.d.ts +25 -0
  58. package/dist/hooks/use_runner_status_effect.d.ts.map +1 -0
  59. package/dist/hooks/use_runner_status_effect.js +33 -0
  60. package/dist/hooks/use_runner_status_effect.js.map +1 -0
  61. package/dist/hooks/use_signal_value.d.ts +39 -0
  62. package/dist/hooks/use_signal_value.d.ts.map +1 -0
  63. package/dist/hooks/use_signal_value.js +51 -0
  64. package/dist/hooks/use_signal_value.js.map +1 -0
  65. package/dist/hooks/use_signal_value_effect.d.ts +35 -0
  66. package/dist/hooks/use_signal_value_effect.d.ts.map +1 -0
  67. package/dist/hooks/use_signal_value_effect.js +54 -0
  68. package/dist/hooks/use_signal_value_effect.js.map +1 -0
  69. package/dist/hooks/use_update.d.ts +15 -0
  70. package/dist/hooks/use_update.d.ts.map +1 -0
  71. package/dist/hooks/use_update.js +24 -0
  72. package/dist/hooks/use_update.js.map +1 -0
  73. package/dist/index.d.ts +22 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +12 -0
  76. package/package.json +79 -0
  77. package/src/__tests__/exports.test.ts +32 -0
  78. package/src/__tests__/hooks.test.tsx +242 -0
  79. package/src/hooks/use_runner_error.ts +29 -0
  80. package/src/hooks/use_runner_error_effect.ts +37 -0
  81. package/src/hooks/use_runner_feedback.ts +31 -0
  82. package/src/hooks/use_runner_feedback_effect.ts +40 -0
  83. package/src/hooks/use_runner_progress.ts +25 -0
  84. package/src/hooks/use_runner_progress_effect.ts +41 -0
  85. package/src/hooks/use_runner_status.ts +27 -0
  86. package/src/hooks/use_runner_status_effect.ts +33 -0
  87. package/src/hooks/use_signal_value.ts +61 -0
  88. package/src/hooks/use_signal_value_effect.ts +68 -0
  89. package/src/hooks/use_update.ts +21 -0
  90. package/src/index.ts +21 -0
  91. package/tsconfig.json +23 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_runner_feedback_effect.js","names":[],"sources":["../../src/hooks/use_runner_feedback_effect.ts"],"sourcesContent":["/**\n * @module hooks/use_runner_feedback_effect\n *\n * This module provides a React hook for handling feedback messages from a Runner.\n * It allows components to react to changes in the feedback state of async operations.\n */\n\nimport { IRunnerBroadcast } from '@j13b/state';\nimport { useSignalValueEffect } from './use_signal_value_effect.js';\n\n/**\n * A React hook that executes a callback whenever the feedback message of a Runner changes.\n *\n * This hook is useful for displaying operation feedback to users, such as loading messages,\n * progress updates, or error notifications. It automatically handles cleanup when the\n * component unmounts.\n *\n * @example\n * ```tsx\n * useRunnerFeedbackEffect(\n * (feedback) => {\n * // Update UI with feedback message\n * setMessage(feedback);\n * },\n * myRunner.broadcast\n * );\n * ```\n *\n * @param callback - A function that will be called with the new feedback message whenever it changes\n * @param task - The Runner's broadcast interface to subscribe to\n * @returns void\n */\nexport function useRunnerFeedbackEffect(\n callback: (feedback: string) => void,\n task: IRunnerBroadcast<any>\n) {\n return useSignalValueEffect(state => {\n callback(state.feedback);\n }, task.stateBroadcast);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,wBACd,UACA,MACA;CACA,OAAO,sBAAqB,UAAS;EACnC,SAAS,MAAM,QAAQ;CACzB,GAAG,KAAK,cAAc;AACxB"}
@@ -0,0 +1,3 @@
1
+ import { IRunnerBroadcast } from '@j13b/state';
2
+ export declare function useRunnerProgress(task: IRunnerBroadcast<any>): number;
3
+ //# sourceMappingURL=use_runner_progress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_runner_progress.d.ts","sourceRoot":"","sources":["../../src/hooks/use_runner_progress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,CAAC,GAAG,CAAC,UAE5D"}
@@ -0,0 +1,9 @@
1
+ import { useSignalValue } from "./use_signal_value.js";
2
+ //#region src/hooks/use_runner_progress.ts
3
+ function useRunnerProgress(task) {
4
+ return useSignalValue(task.stateBroadcast).progress;
5
+ }
6
+ //#endregion
7
+ export { useRunnerProgress };
8
+
9
+ //# sourceMappingURL=use_runner_progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_runner_progress.js","names":[],"sources":["../../src/hooks/use_runner_progress.ts"],"sourcesContent":["/**\n * A React hook that subscribes to the progress state of a Runner.\n *\n * @template T - The type of the Runner's value\n * @param {IRunnerBroadcast<T>} task - The Runner broadcast interface to subscribe to\n * @returns {number} A number between 0 and 1 representing the current progress of the task\n *\n * @example\n * ```tsx\n * const progress = useRunnerProgress(saveRunner.broadcast);\n * // progress will be a number between 0 and 1\n * // 0 = not started, 1 = complete\n * ```\n *\n * @remarks\n * This hook is useful for tracking the progress of long-running operations\n * such as file uploads, data processing, or any task that can report progress.\n * The component will automatically re-render when the progress value changes.\n */\nimport { IRunnerBroadcast } from '@j13b/state';\nimport { useSignalValue } from './use_signal_value.js';\n\nexport function useRunnerProgress(task: IRunnerBroadcast<any>) {\n return useSignalValue(task.stateBroadcast).progress;\n}\n"],"mappings":";;AAsBA,SAAgB,kBAAkB,MAA6B;CAC7D,OAAO,eAAe,KAAK,cAAc,CAAC,CAAC;AAC7C"}
@@ -0,0 +1,26 @@
1
+ import { IRunnerBroadcast } from '@j13b/state';
2
+ /**
3
+ * Hook that executes a callback whenever the progress of a Runner operation changes.
4
+ *
5
+ * @template T - The type of the Runner's value
6
+ * @param callback - Function to be called when progress changes. Receives the current progress value (0-1)
7
+ * @param task - The Runner broadcast interface to track progress from
8
+ * @returns void
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * useRunnerProgressEffect(
13
+ * (progress) => {
14
+ * console.log(`Operation is ${progress * 100}% complete`);
15
+ * },
16
+ * myRunner.broadcast
17
+ * );
18
+ * ```
19
+ *
20
+ * @remarks
21
+ * - The callback will be called with a number between 0 and 1 representing progress
22
+ * - The effect is automatically cleaned up when the component unmounts
23
+ * - This hook is built on top of useSignalValueEffect for efficient state tracking
24
+ */
25
+ export declare function useRunnerProgressEffect(callback: (progress: number) => void, task: IRunnerBroadcast<any>): void;
26
+ //# sourceMappingURL=use_runner_progress_effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_runner_progress_effect.d.ts","sourceRoot":"","sources":["../../src/hooks/use_runner_progress_effect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,IAAI,EAAE,gBAAgB,CAAC,GAAG,CAAC,QAK5B"}
@@ -0,0 +1,34 @@
1
+ import { useSignalValueEffect } from "./use_signal_value_effect.js";
2
+ //#region src/hooks/use_runner_progress_effect.ts
3
+ /**
4
+ * Hook that executes a callback whenever the progress of a Runner operation changes.
5
+ *
6
+ * @template T - The type of the Runner's value
7
+ * @param callback - Function to be called when progress changes. Receives the current progress value (0-1)
8
+ * @param task - The Runner broadcast interface to track progress from
9
+ * @returns void
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * useRunnerProgressEffect(
14
+ * (progress) => {
15
+ * console.log(`Operation is ${progress * 100}% complete`);
16
+ * },
17
+ * myRunner.broadcast
18
+ * );
19
+ * ```
20
+ *
21
+ * @remarks
22
+ * - The callback will be called with a number between 0 and 1 representing progress
23
+ * - The effect is automatically cleaned up when the component unmounts
24
+ * - This hook is built on top of useSignalValueEffect for efficient state tracking
25
+ */
26
+ function useRunnerProgressEffect(callback, task) {
27
+ return useSignalValueEffect((state) => {
28
+ callback(state.progress);
29
+ }, task.stateBroadcast);
30
+ }
31
+ //#endregion
32
+ export { useRunnerProgressEffect };
33
+
34
+ //# sourceMappingURL=use_runner_progress_effect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_runner_progress_effect.js","names":[],"sources":["../../src/hooks/use_runner_progress_effect.ts"],"sourcesContent":["/**\n * @module hooks/use_runner_progress_effect\n *\n * This module provides a React hook for tracking the progress of a Runner operation.\n * It allows components to react to progress changes in long-running operations.\n */\n\nimport { IRunnerBroadcast } from '@j13b/state';\nimport { useSignalValueEffect } from './use_signal_value_effect.js';\n\n/**\n * Hook that executes a callback whenever the progress of a Runner operation changes.\n *\n * @template T - The type of the Runner's value\n * @param callback - Function to be called when progress changes. Receives the current progress value (0-1)\n * @param task - The Runner broadcast interface to track progress from\n * @returns void\n *\n * @example\n * ```tsx\n * useRunnerProgressEffect(\n * (progress) => {\n * console.log(`Operation is ${progress * 100}% complete`);\n * },\n * myRunner.broadcast\n * );\n * ```\n *\n * @remarks\n * - The callback will be called with a number between 0 and 1 representing progress\n * - The effect is automatically cleaned up when the component unmounts\n * - This hook is built on top of useSignalValueEffect for efficient state tracking\n */\nexport function useRunnerProgressEffect(\n callback: (progress: number) => void,\n task: IRunnerBroadcast<any>\n) {\n return useSignalValueEffect(state => {\n callback(state.progress);\n }, task.stateBroadcast);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,wBACd,UACA,MACA;CACA,OAAO,sBAAqB,UAAS;EACnC,SAAS,MAAM,QAAQ;CACzB,GAAG,KAAK,cAAc;AACxB"}
@@ -0,0 +1,24 @@
1
+ import { IRunnerBroadcast } from '@j13b/state';
2
+ /**
3
+ * A hook that subscribes to a runner's status changes and returns the current status.
4
+ * The component will re-render whenever the runner's status changes.
5
+ *
6
+ * @template T - The type of the runner's value
7
+ * @param broadcast - The broadcast interface of the runner to subscribe to
8
+ * @returns The current status of the runner (INITIAL, PENDING, SUCCESS, ERROR)
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const status = useRunnerStatus(runner.broadcast);
13
+ *
14
+ * return (
15
+ * <div>
16
+ * {status === 'PENDING' && <Spinner />}
17
+ * {status === 'ERROR' && <ErrorMessage />}
18
+ * {status === 'SUCCESS' && <SuccessMessage />}
19
+ * </div>
20
+ * );
21
+ * ```
22
+ */
23
+ export declare function useRunnerStatus(broadcast: IRunnerBroadcast<any>): import('@j13b/state').Status;
24
+ //# sourceMappingURL=use_runner_status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_runner_status.d.ts","sourceRoot":"","sources":["../../src/hooks/use_runner_status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAC,gCAE/D"}
@@ -0,0 +1,30 @@
1
+ import { useSignalValue } from "./use_signal_value.js";
2
+ //#region src/hooks/use_runner_status.ts
3
+ /**
4
+ * A hook that subscribes to a runner's status changes and returns the current status.
5
+ * The component will re-render whenever the runner's status changes.
6
+ *
7
+ * @template T - The type of the runner's value
8
+ * @param broadcast - The broadcast interface of the runner to subscribe to
9
+ * @returns The current status of the runner (INITIAL, PENDING, SUCCESS, ERROR)
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const status = useRunnerStatus(runner.broadcast);
14
+ *
15
+ * return (
16
+ * <div>
17
+ * {status === 'PENDING' && <Spinner />}
18
+ * {status === 'ERROR' && <ErrorMessage />}
19
+ * {status === 'SUCCESS' && <SuccessMessage />}
20
+ * </div>
21
+ * );
22
+ * ```
23
+ */
24
+ function useRunnerStatus(broadcast) {
25
+ return useSignalValue(broadcast.stateBroadcast).status;
26
+ }
27
+ //#endregion
28
+ export { useRunnerStatus };
29
+
30
+ //# sourceMappingURL=use_runner_status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_runner_status.js","names":[],"sources":["../../src/hooks/use_runner_status.ts"],"sourcesContent":["import { IRunnerBroadcast } from '@j13b/state';\nimport { useSignalValue } from './use_signal_value.js';\n\n/**\n * A hook that subscribes to a runner's status changes and returns the current status.\n * The component will re-render whenever the runner's status changes.\n *\n * @template T - The type of the runner's value\n * @param broadcast - The broadcast interface of the runner to subscribe to\n * @returns The current status of the runner (INITIAL, PENDING, SUCCESS, ERROR)\n *\n * @example\n * ```tsx\n * const status = useRunnerStatus(runner.broadcast);\n *\n * return (\n * <div>\n * {status === 'PENDING' && <Spinner />}\n * {status === 'ERROR' && <ErrorMessage />}\n * {status === 'SUCCESS' && <SuccessMessage />}\n * </div>\n * );\n * ```\n */\nexport function useRunnerStatus(broadcast: IRunnerBroadcast<any>) {\n return useSignalValue(broadcast.stateBroadcast).status;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,gBAAgB,WAAkC;CAChE,OAAO,eAAe,UAAU,cAAc,CAAC,CAAC;AAClD"}
@@ -0,0 +1,25 @@
1
+ import { IRunnerBroadcast, Status } from '@j13b/state';
2
+ /**
3
+ * A hook that runs an effect whenever a runner's status changes.
4
+ * This is useful for side effects that need to respond to runner status changes
5
+ * without causing a re-render.
6
+ *
7
+ * @template T - The type of the runner's value
8
+ * @param callback - The effect to run when the runner's status changes
9
+ * @param task - The broadcast interface of the runner to subscribe to
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * useRunnerStatusEffect(
14
+ * status => {
15
+ * if (status === 'ERROR') {
16
+ * // Handle error state
17
+ * showErrorToast();
18
+ * }
19
+ * },
20
+ * runner.broadcast
21
+ * );
22
+ * ```
23
+ */
24
+ export declare function useRunnerStatusEffect(callback: (value: Status) => void, task: IRunnerBroadcast<any>): void;
25
+ //# sourceMappingURL=use_runner_status_effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_runner_status_effect.d.ts","sourceRoot":"","sources":["../../src/hooks/use_runner_status_effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGvD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,IAAI,EAAE,gBAAgB,CAAC,GAAG,CAAC,QAK5B"}
@@ -0,0 +1,33 @@
1
+ import { useSignalValueEffect } from "./use_signal_value_effect.js";
2
+ //#region src/hooks/use_runner_status_effect.ts
3
+ /**
4
+ * A hook that runs an effect whenever a runner's status changes.
5
+ * This is useful for side effects that need to respond to runner status changes
6
+ * without causing a re-render.
7
+ *
8
+ * @template T - The type of the runner's value
9
+ * @param callback - The effect to run when the runner's status changes
10
+ * @param task - The broadcast interface of the runner to subscribe to
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * useRunnerStatusEffect(
15
+ * status => {
16
+ * if (status === 'ERROR') {
17
+ * // Handle error state
18
+ * showErrorToast();
19
+ * }
20
+ * },
21
+ * runner.broadcast
22
+ * );
23
+ * ```
24
+ */
25
+ function useRunnerStatusEffect(callback, task) {
26
+ return useSignalValueEffect((state) => {
27
+ callback(state.status);
28
+ }, task.stateBroadcast);
29
+ }
30
+ //#endregion
31
+ export { useRunnerStatusEffect };
32
+
33
+ //# sourceMappingURL=use_runner_status_effect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_runner_status_effect.js","names":[],"sources":["../../src/hooks/use_runner_status_effect.ts"],"sourcesContent":["import { IRunnerBroadcast, Status } from '@j13b/state';\nimport { useSignalValueEffect } from './use_signal_value_effect.js';\n\n/**\n * A hook that runs an effect whenever a runner's status changes.\n * This is useful for side effects that need to respond to runner status changes\n * without causing a re-render.\n *\n * @template T - The type of the runner's value\n * @param callback - The effect to run when the runner's status changes\n * @param task - The broadcast interface of the runner to subscribe to\n *\n * @example\n * ```tsx\n * useRunnerStatusEffect(\n * status => {\n * if (status === 'ERROR') {\n * // Handle error state\n * showErrorToast();\n * }\n * },\n * runner.broadcast\n * );\n * ```\n */\nexport function useRunnerStatusEffect(\n callback: (value: Status) => void,\n task: IRunnerBroadcast<any>\n) {\n return useSignalValueEffect(state => {\n callback(state.status);\n }, task.stateBroadcast);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,sBACd,UACA,MACA;CACA,OAAO,sBAAqB,UAAS;EACnC,SAAS,MAAM,MAAM;CACvB,GAAG,KAAK,cAAc;AACxB"}
@@ -0,0 +1,39 @@
1
+ import { IBroadcast } from '@j13b/state';
2
+ /**
3
+ * A hook that subscribes to a signal's value changes and returns the current value.
4
+ * The component re-renders when the signal broadcasts. Notifications are batched
5
+ * by React: a burst of synchronous emissions (e.g. several `set()` calls in one
6
+ * event tick) renders once with the latest value — intermediate states are never
7
+ * painted.
8
+ *
9
+ * Built on React 18's `useSyncExternalStore`, which makes the hook:
10
+ * - Tear-free under concurrent rendering (React re-checks the snapshot after render).
11
+ * - SSR-safe: on the server the current value is rendered via the server snapshot
12
+ * without subscribing (no `useLayoutEffect` warnings).
13
+ *
14
+ * The snapshot is the broadcast's monotonically increasing `version` rather than
15
+ * the value itself. This guarantees that no emission is silently ignored:
16
+ * `Signal.transform()` mutates the value in place and returns the SAME reference,
17
+ * so value/reference equality (`Object.is`) cannot detect the change — the version
18
+ * counter is what makes in-place transforms render. A consequence is that a
19
+ * `set()` of an identical value also triggers one render, since without a deep
20
+ * comparison it is indistinguishable from an in-place mutation.
21
+ *
22
+ * The subscription object returned by `broadcast.subscribe()` is strongly held by
23
+ * the unsubscribe closure for the lifetime of the subscription (the signal itself
24
+ * only holds it via WeakRef), and is unsubscribed when the component unmounts or
25
+ * the broadcast changes.
26
+ *
27
+ * @template TValue - The type of the signal's value
28
+ * @param broadcast - The broadcast interface of the signal to subscribe to
29
+ * @returns The current value of the signal
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * const value = useSignalValue(signal.broadcast);
34
+ *
35
+ * return <div>Current value: {value}</div>;
36
+ * ```
37
+ */
38
+ export declare function useSignalValue<TValue>(broadcast: IBroadcast<TValue>): TValue;
39
+ //# sourceMappingURL=use_signal_value.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_signal_value.d.ts","sourceRoot":"","sources":["../../src/hooks/use_signal_value.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,MAAM,CAAC,UAqBnE"}
@@ -0,0 +1,51 @@
1
+ import { useCallback, useSyncExternalStore } from "react";
2
+ //#region src/hooks/use_signal_value.ts
3
+ /**
4
+ * A hook that subscribes to a signal's value changes and returns the current value.
5
+ * The component re-renders when the signal broadcasts. Notifications are batched
6
+ * by React: a burst of synchronous emissions (e.g. several `set()` calls in one
7
+ * event tick) renders once with the latest value — intermediate states are never
8
+ * painted.
9
+ *
10
+ * Built on React 18's `useSyncExternalStore`, which makes the hook:
11
+ * - Tear-free under concurrent rendering (React re-checks the snapshot after render).
12
+ * - SSR-safe: on the server the current value is rendered via the server snapshot
13
+ * without subscribing (no `useLayoutEffect` warnings).
14
+ *
15
+ * The snapshot is the broadcast's monotonically increasing `version` rather than
16
+ * the value itself. This guarantees that no emission is silently ignored:
17
+ * `Signal.transform()` mutates the value in place and returns the SAME reference,
18
+ * so value/reference equality (`Object.is`) cannot detect the change — the version
19
+ * counter is what makes in-place transforms render. A consequence is that a
20
+ * `set()` of an identical value also triggers one render, since without a deep
21
+ * comparison it is indistinguishable from an in-place mutation.
22
+ *
23
+ * The subscription object returned by `broadcast.subscribe()` is strongly held by
24
+ * the unsubscribe closure for the lifetime of the subscription (the signal itself
25
+ * only holds it via WeakRef), and is unsubscribed when the component unmounts or
26
+ * the broadcast changes.
27
+ *
28
+ * @template TValue - The type of the signal's value
29
+ * @param broadcast - The broadcast interface of the signal to subscribe to
30
+ * @returns The current value of the signal
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * const value = useSignalValue(signal.broadcast);
35
+ *
36
+ * return <div>Current value: {value}</div>;
37
+ * ```
38
+ */
39
+ function useSignalValue(broadcast) {
40
+ const subscribe = useCallback((onStoreChange) => {
41
+ const subscription = broadcast.subscribe(onStoreChange);
42
+ return () => subscription.unsubscribe();
43
+ }, [broadcast]);
44
+ const getSnapshot = useCallback(() => broadcast.version, [broadcast]);
45
+ useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
46
+ return broadcast.get();
47
+ }
48
+ //#endregion
49
+ export { useSignalValue };
50
+
51
+ //# sourceMappingURL=use_signal_value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_signal_value.js","names":[],"sources":["../../src/hooks/use_signal_value.ts"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\nimport { IBroadcast } from '@j13b/state';\n\n/**\n * A hook that subscribes to a signal's value changes and returns the current value.\n * The component re-renders when the signal broadcasts. Notifications are batched\n * by React: a burst of synchronous emissions (e.g. several `set()` calls in one\n * event tick) renders once with the latest value — intermediate states are never\n * painted.\n *\n * Built on React 18's `useSyncExternalStore`, which makes the hook:\n * - Tear-free under concurrent rendering (React re-checks the snapshot after render).\n * - SSR-safe: on the server the current value is rendered via the server snapshot\n * without subscribing (no `useLayoutEffect` warnings).\n *\n * The snapshot is the broadcast's monotonically increasing `version` rather than\n * the value itself. This guarantees that no emission is silently ignored:\n * `Signal.transform()` mutates the value in place and returns the SAME reference,\n * so value/reference equality (`Object.is`) cannot detect the change — the version\n * counter is what makes in-place transforms render. A consequence is that a\n * `set()` of an identical value also triggers one render, since without a deep\n * comparison it is indistinguishable from an in-place mutation.\n *\n * The subscription object returned by `broadcast.subscribe()` is strongly held by\n * the unsubscribe closure for the lifetime of the subscription (the signal itself\n * only holds it via WeakRef), and is unsubscribed when the component unmounts or\n * the broadcast changes.\n *\n * @template TValue - The type of the signal's value\n * @param broadcast - The broadcast interface of the signal to subscribe to\n * @returns The current value of the signal\n *\n * @example\n * ```tsx\n * const value = useSignalValue(signal.broadcast);\n *\n * return <div>Current value: {value}</div>;\n * ```\n */\nexport function useSignalValue<TValue>(broadcast: IBroadcast<TValue>) {\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n // `subscription` must be strongly referenced: the signal only keeps a\n // WeakRef to it, so this closure is what keeps the callback alive.\n const subscription = broadcast.subscribe(onStoreChange);\n return () => subscription.unsubscribe();\n },\n [broadcast]\n );\n\n // Version snapshot (deliberate): transform() mutates in place and returns the\n // same reference, so Object.is on the value would miss changes. The version\n // increments on every set(), so no emission is ignored (React coalesces a\n // synchronous burst into a single render of the latest value). Do not\n // snapshot the value.\n const getSnapshot = useCallback(() => broadcast.version, [broadcast]);\n\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n return broadcast.get();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,eAAuB,WAA+B;CACpE,MAAM,YAAY,aACf,kBAA8B;EAG7B,MAAM,eAAe,UAAU,UAAU,aAAa;EACtD,aAAa,aAAa,YAAY;CACxC,GACA,CAAC,SAAS,CACZ;CAOA,MAAM,cAAc,kBAAkB,UAAU,SAAS,CAAC,SAAS,CAAC;CAEpE,qBAAqB,WAAW,aAAa,WAAW;CAExD,OAAO,UAAU,IAAI;AACvB"}
@@ -0,0 +1,35 @@
1
+ import { IBroadcast } from '@j13b/state';
2
+ /**
3
+ * A hook that runs an effect whenever a signal's value changes.
4
+ * This is useful for side effects that need to respond to signal value changes
5
+ * without causing a re-render.
6
+ *
7
+ * Contract:
8
+ * - The callback fires exactly once with the current value on mount, and again
9
+ * once whenever the `broadcast` identity changes (with the new broadcast's
10
+ * current value).
11
+ * - After that, it fires exactly once per emission — including emissions of an
12
+ * identical value (every `set()` notifies subscribers). No double-fires.
13
+ * - The latest `callback` prop is always used; changing the callback does not
14
+ * re-subscribe or re-fire.
15
+ * - The subscription object is strongly held for the component's lifetime (the
16
+ * signal itself only holds it via WeakRef) and is cleaned up on unmount or
17
+ * when the broadcast changes.
18
+ *
19
+ * @template T - The type of the signal's value
20
+ * @param callback - The effect to run when the signal's value changes
21
+ * @param broadcast - The broadcast interface of the signal to subscribe to
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * useSignalValueEffect(
26
+ * value => {
27
+ * // Do something with the new value
28
+ * console.log('Value changed:', value);
29
+ * },
30
+ * signal.broadcast
31
+ * );
32
+ * ```
33
+ */
34
+ export declare function useSignalValueEffect<T>(callback: (value: T) => void, broadcast: IBroadcast<T>): void;
35
+ //# sourceMappingURL=use_signal_value_effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_signal_value_effect.d.ts","sourceRoot":"","sources":["../../src/hooks/use_signal_value_effect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAO9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAC5B,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,QAyBzB"}
@@ -0,0 +1,54 @@
1
+ import { useEffect, useLayoutEffect, useRef } from "react";
2
+ //#region src/hooks/use_signal_value_effect.ts
3
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
4
+ /**
5
+ * A hook that runs an effect whenever a signal's value changes.
6
+ * This is useful for side effects that need to respond to signal value changes
7
+ * without causing a re-render.
8
+ *
9
+ * Contract:
10
+ * - The callback fires exactly once with the current value on mount, and again
11
+ * once whenever the `broadcast` identity changes (with the new broadcast's
12
+ * current value).
13
+ * - After that, it fires exactly once per emission — including emissions of an
14
+ * identical value (every `set()` notifies subscribers). No double-fires.
15
+ * - The latest `callback` prop is always used; changing the callback does not
16
+ * re-subscribe or re-fire.
17
+ * - The subscription object is strongly held for the component's lifetime (the
18
+ * signal itself only holds it via WeakRef) and is cleaned up on unmount or
19
+ * when the broadcast changes.
20
+ *
21
+ * @template T - The type of the signal's value
22
+ * @param callback - The effect to run when the signal's value changes
23
+ * @param broadcast - The broadcast interface of the signal to subscribe to
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * useSignalValueEffect(
28
+ * value => {
29
+ * // Do something with the new value
30
+ * console.log('Value changed:', value);
31
+ * },
32
+ * signal.broadcast
33
+ * );
34
+ * ```
35
+ */
36
+ function useSignalValueEffect(callback, broadcast) {
37
+ const callbackRef = useRef(callback);
38
+ useIsomorphicLayoutEffect(() => {
39
+ callbackRef.current = callback;
40
+ }, [callback]);
41
+ useIsomorphicLayoutEffect(() => {
42
+ const subscription = broadcast.subscribe((value) => {
43
+ callbackRef.current(value);
44
+ });
45
+ callbackRef.current(broadcast.get());
46
+ return () => {
47
+ subscription.unsubscribe();
48
+ };
49
+ }, [broadcast]);
50
+ }
51
+ //#endregion
52
+ export { useSignalValueEffect };
53
+
54
+ //# sourceMappingURL=use_signal_value_effect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_signal_value_effect.js","names":[],"sources":["../../src/hooks/use_signal_value_effect.ts"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef } from 'react';\nimport type { IBroadcast } from '@j13b/state';\n\n// useLayoutEffect warns during server-side rendering; fall back to useEffect\n// there. Effects never run on the server either way, so behavior is identical.\nconst useIsomorphicLayoutEffect =\n typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\n/**\n * A hook that runs an effect whenever a signal's value changes.\n * This is useful for side effects that need to respond to signal value changes\n * without causing a re-render.\n *\n * Contract:\n * - The callback fires exactly once with the current value on mount, and again\n * once whenever the `broadcast` identity changes (with the new broadcast's\n * current value).\n * - After that, it fires exactly once per emission — including emissions of an\n * identical value (every `set()` notifies subscribers). No double-fires.\n * - The latest `callback` prop is always used; changing the callback does not\n * re-subscribe or re-fire.\n * - The subscription object is strongly held for the component's lifetime (the\n * signal itself only holds it via WeakRef) and is cleaned up on unmount or\n * when the broadcast changes.\n *\n * @template T - The type of the signal's value\n * @param callback - The effect to run when the signal's value changes\n * @param broadcast - The broadcast interface of the signal to subscribe to\n *\n * @example\n * ```tsx\n * useSignalValueEffect(\n * value => {\n * // Do something with the new value\n * console.log('Value changed:', value);\n * },\n * signal.broadcast\n * );\n * ```\n */\nexport function useSignalValueEffect<T>(\n callback: (value: T) => void,\n broadcast: IBroadcast<T>\n) {\n const callbackRef = useRef(callback);\n\n // Declared before the subscription effect so the ref is up to date when the\n // subscription effect (re-)runs in the same commit.\n useIsomorphicLayoutEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n useIsomorphicLayoutEffect(() => {\n // `subscription` is strongly referenced by this effect's closure: the\n // signal only keeps a WeakRef to it, so this is what keeps it alive.\n const subscription = broadcast.subscribe(value => {\n callbackRef.current(value);\n });\n\n // Fire exactly once with the current value on mount / broadcast change.\n // Subsequent emissions arrive through the subscription above.\n callbackRef.current(broadcast.get());\n\n return () => {\n subscription.unsubscribe();\n };\n }, [broadcast]);\n}\n"],"mappings":";;AAKA,IAAM,4BACJ,OAAO,WAAW,cAAc,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCpD,SAAgB,qBACd,UACA,WACA;CACA,MAAM,cAAc,OAAO,QAAQ;CAInC,gCAAgC;EAC9B,YAAY,UAAU;CACxB,GAAG,CAAC,QAAQ,CAAC;CAEb,gCAAgC;EAG9B,MAAM,eAAe,UAAU,WAAU,UAAS;GAChD,YAAY,QAAQ,KAAK;EAC3B,CAAC;EAID,YAAY,QAAQ,UAAU,IAAI,CAAC;EAEnC,aAAa;GACX,aAAa,YAAY;EAC3B;CACF,GAAG,CAAC,SAAS,CAAC;AAChB"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * A hook that provides a way to force component re-renders.
3
+ *
4
+ * @returns A function that triggers a re-render when called
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * const update = useUpdate();
9
+ *
10
+ * // Force a re-render
11
+ * update();
12
+ * ```
13
+ */
14
+ export declare const useUpdate: () => () => void;
15
+ //# sourceMappingURL=use_update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_update.d.ts","sourceRoot":"","sources":["../../src/hooks/use_update.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS,QAEH,MAAM,IACxB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { useReducer } from "react";
2
+ //#region src/hooks/use_update.ts
3
+ var updateReducer = (num) => (num + 1) % 1e6;
4
+ /**
5
+ * A hook that provides a way to force component re-renders.
6
+ *
7
+ * @returns A function that triggers a re-render when called
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const update = useUpdate();
12
+ *
13
+ * // Force a re-render
14
+ * update();
15
+ * ```
16
+ */
17
+ var useUpdate = () => {
18
+ const [, update] = useReducer(updateReducer, 0);
19
+ return update;
20
+ };
21
+ //#endregion
22
+ export { useUpdate };
23
+
24
+ //# sourceMappingURL=use_update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use_update.js","names":[],"sources":["../../src/hooks/use_update.ts"],"sourcesContent":["import { useReducer } from 'react';\n\nconst updateReducer = (num: number): number => (num + 1) % 1_000_000;\n\n/**\n * A hook that provides a way to force component re-renders.\n *\n * @returns A function that triggers a re-render when called\n *\n * @example\n * ```tsx\n * const update = useUpdate();\n *\n * // Force a re-render\n * update();\n * ```\n */\nexport const useUpdate = () => {\n const [, update] = useReducer(updateReducer, 0);\n return update as () => void;\n};\n"],"mappings":";;AAEA,IAAM,iBAAiB,SAAyB,MAAM,KAAK;;;;;;;;;;;;;;AAe3D,IAAa,kBAAkB;CAC7B,MAAM,GAAG,UAAU,WAAW,eAAe,CAAC;CAC9C,OAAO;AACT"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * React bindings for @j13b/state.
3
+ * Hooks for subscribing to Signals, Runners, and Broadcasts from React
4
+ * components. Requires react and @j13b/state as peer dependencies.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { useSignalValue, useRunnerStatus } from '@j13b/react-state';
9
+ * ```
10
+ */
11
+ export * from './hooks/use_signal_value.js';
12
+ export * from './hooks/use_signal_value_effect.js';
13
+ export * from './hooks/use_update.js';
14
+ export * from './hooks/use_runner_status.js';
15
+ export * from './hooks/use_runner_status_effect.js';
16
+ export * from './hooks/use_runner_error.js';
17
+ export * from './hooks/use_runner_error_effect.js';
18
+ export * from './hooks/use_runner_progress.js';
19
+ export * from './hooks/use_runner_progress_effect.js';
20
+ export * from './hooks/use_runner_feedback.js';
21
+ export * from './hooks/use_runner_feedback_effect.js';
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oCAAoC,CAAC;AACnD,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { useSignalValue } from "./hooks/use_signal_value.js";
2
+ import { useSignalValueEffect } from "./hooks/use_signal_value_effect.js";
3
+ import { useUpdate } from "./hooks/use_update.js";
4
+ import { useRunnerStatus } from "./hooks/use_runner_status.js";
5
+ import { useRunnerStatusEffect } from "./hooks/use_runner_status_effect.js";
6
+ import { useRunnerError } from "./hooks/use_runner_error.js";
7
+ import { useRunnerErrorEffect } from "./hooks/use_runner_error_effect.js";
8
+ import { useRunnerProgress } from "./hooks/use_runner_progress.js";
9
+ import { useRunnerProgressEffect } from "./hooks/use_runner_progress_effect.js";
10
+ import { useRunnerFeedback } from "./hooks/use_runner_feedback.js";
11
+ import { useRunnerFeedbackEffect } from "./hooks/use_runner_feedback_effect.js";
12
+ export { useRunnerError, useRunnerErrorEffect, useRunnerFeedback, useRunnerFeedbackEffect, useRunnerProgress, useRunnerProgressEffect, useRunnerStatus, useRunnerStatusEffect, useSignalValue, useSignalValueEffect, useUpdate };
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@j13b/react-state",
3
+ "version": "0.1.0",
4
+ "description": "React bindings for @j13b/state — hooks for Signals, Runners, and Broadcasts.",
5
+ "license": "Apache-2.0",
6
+ "author": "Jared J Barnes",
7
+ "type": "module",
8
+ "main": "./dist/cjs/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "require": {
18
+ "types": "./dist/cjs/index.d.ts",
19
+ "default": "./dist/cjs/index.js"
20
+ }
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src",
27
+ "tsconfig.json",
28
+ "LICENSE",
29
+ "NOTICE",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "build": "vite build && node scripts/build-cjs.mjs",
34
+ "check:types": "tsc --noEmit",
35
+ "test": "vitest",
36
+ "test:run": "vitest run",
37
+ "prepublishOnly": "npm run check:types && npm run test:run && npm run build"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/jaredjbarnes/react-state.git"
42
+ },
43
+ "homepage": "https://github.com/jaredjbarnes/react-state#readme",
44
+ "bugs": {
45
+ "url": "https://github.com/jaredjbarnes/react-state/issues"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "keywords": [
51
+ "react",
52
+ "hooks",
53
+ "signal",
54
+ "signals",
55
+ "state",
56
+ "reactive",
57
+ "broadcast",
58
+ "subscription"
59
+ ],
60
+ "peerDependencies": {
61
+ "@j13b/state": "^0.2.0",
62
+ "react": "^18.2.0",
63
+ "react-dom": "^18.2.0"
64
+ },
65
+ "devDependencies": {
66
+ "@j13b/state": "^0.2.0",
67
+ "@testing-library/react": "^14.3.1",
68
+ "@types/node": "^20.0.0",
69
+ "@types/react": "^18.2.0",
70
+ "@types/react-dom": "^18.2.0",
71
+ "jsdom": "^24.1.3",
72
+ "react": "^18.2.0",
73
+ "react-dom": "^18.2.0",
74
+ "typescript": "^5.4.0",
75
+ "vite": "^8.1.3",
76
+ "vite-plugin-dts": "^4.2.0",
77
+ "vitest": "^4.1.9"
78
+ }
79
+ }