@actuallab/fusion-react 14.3.26 → 14.3.32

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 (2) hide show
  1. package/README.md +108 -0
  2. package/package.json +3 -3
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # @actuallab/fusion-react
2
+
3
+ [![npm](https://img.shields.io/npm/v/@actuallab/fusion-react)](https://www.npmjs.com/package/@actuallab/fusion-react)
4
+ [![Documentation](https://img.shields.io/badge/Documentation-6B5B95)](https://fusion.actuallab.net/PartTS-React)
5
+ [![License](https://img.shields.io/npm/l/@actuallab/fusion-react)](https://github.com/ActualLab/Fusion/blob/master/LICENSE)
6
+
7
+ React hooks that plug [Fusion](https://fusion.actuallab.net/)'s reactive states into React's
8
+ rendering lifecycle — the TypeScript equivalent of `ComputedStateComponent<T>` in
9
+ `ActualLab.Fusion.Blazor`. When server-side data changes and the invalidation reaches the client,
10
+ your component re-renders. No polling, no manual subscriptions, no store wiring.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @actuallab/fusion-react
16
+ ```
17
+
18
+ Peer dependency: **React ^19**. ESM-first with a CJS fallback; ships its own `.d.ts`.
19
+
20
+ ## useComputedState
21
+
22
+ ```tsx
23
+ import { useComputedState } from '@actuallab/fusion-react';
24
+
25
+ function TodoList({ api }: { api: ITodoApi }) {
26
+ const { value, error, isInitial } = useComputedState(
27
+ () => api.ListIds('~', 10),
28
+ [api],
29
+ );
30
+
31
+ if (isInitial) return <p>Loading...</p>;
32
+ if (error) return <p>Error: {String(error)}</p>;
33
+ return <ul>{value?.map(id => <li key={id}>{id}</li>)}</ul>;
34
+ }
35
+ ```
36
+
37
+ Anything the `computer` calls becomes a dependency, so the hook re-runs when any of it is
38
+ invalidated. `deps` works like React's — changing them disposes the old `ComputedState` and builds
39
+ a new one.
40
+
41
+ The hook is built on `useSyncExternalStore`, and the state is created and disposed **inside
42
+ `subscribe`**, never during render: StrictMode double-mounts and discarded concurrent renders are
43
+ both safe. That's also why `state` is `undefined` on the very first, pre-effect render — guard on
44
+ `isInitial`.
45
+
46
+ Pace recomputation with the `updateDelayer` option:
47
+
48
+ ```tsx
49
+ import { FixedDelayer, UIUpdateDelayer } from '@actuallab/fusion';
50
+
51
+ // 500 ms after invalidation…
52
+ useComputedState(() => api.GetSummary('~'), [api], { updateDelayer: FixedDelayer.get(500) });
53
+
54
+ // …but immediately while a UI action is running (or just finished)
55
+ useComputedState(() => api.GetSummary('~'), [api], { updateDelayer: UIUpdateDelayer.get(500) });
56
+ ```
57
+
58
+ ## useMutableState
59
+
60
+ A manually-settable reactive value that both re-renders the component and participates in the
61
+ Fusion dependency graph:
62
+
63
+ ```tsx
64
+ import { useComputedState, useMutableState } from '@actuallab/fusion-react';
65
+
66
+ function SearchResults({ api }: { api: ISearchApi }) {
67
+ const { value: query, set: setQuery, state: queryState } = useMutableState('');
68
+
69
+ const { value: results } = useComputedState(
70
+ () => {
71
+ const q = queryState.use(); // registers the dependency
72
+ return q ? api.Search(q) : [];
73
+ },
74
+ [api, queryState],
75
+ );
76
+
77
+ return (
78
+ <>
79
+ <input value={query ?? ''} onChange={e => setQuery(e.target.value)} />
80
+ <ul>{results?.map(r => <li key={r.id}>{r.title}</li>)}</ul>
81
+ </>
82
+ );
83
+ }
84
+ ```
85
+
86
+ `value` reads through `valueOrUndefined`, so it never throws — an error stored with
87
+ `set(errorResult(e))` surfaces as `error` and re-renders normally instead of unmounting the tree.
88
+
89
+ ## API surface
90
+
91
+ | Export | Description |
92
+ |--------|-------------|
93
+ | `useComputedState(computer, deps, options?)` | Returns `{ value, error, isInitial, state }` |
94
+ | `useMutableState(initial)` | Returns `{ value, error, set, state }` |
95
+ | `UIActionTracker`, `uiActions`, `UIUpdateDelayer` | Re-exported from `@actuallab/fusion` for convenience |
96
+
97
+ For a connection-status banner, pair `RpcPeerStateMonitor` from `@actuallab/rpc` with a plain
98
+ `useState` + `useEffect` — see the [docs](https://fusion.actuallab.net/PartTS-React#connection-status-ui).
99
+
100
+ ## Documentation
101
+
102
+ - [`@actuallab/fusion-react` reference](https://fusion.actuallab.net/PartTS-React) — signatures, lifecycle, `AsyncContext` notes
103
+ - [TypeScript port overview](https://fusion.actuallab.net/PartTS)
104
+ - [TodoApp sample (React + Fusion)](https://github.com/ActualLab/Fusion.Samples/tree/master/src/TodoApp)
105
+
106
+ ## License
107
+
108
+ MIT — see [LICENSE](https://github.com/ActualLab/Fusion/blob/master/LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actuallab/fusion-react",
3
- "version": "14.3.26",
3
+ "version": "14.3.32",
4
4
  "description": "React integration for Fusion - hooks and UI action tracking",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -26,8 +26,8 @@
26
26
  "clean": "rm -rf dist"
27
27
  },
28
28
  "dependencies": {
29
- "@actuallab/core": "14.3.26",
30
- "@actuallab/fusion": "14.3.26"
29
+ "@actuallab/core": "14.3.32",
30
+ "@actuallab/fusion": "14.3.32"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "react": "^19.0.0"