@actuallab/fusion-react 14.3.16 → 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.
- package/README.md +108 -0
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @actuallab/fusion-react
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@actuallab/fusion-react)
|
|
4
|
+
[](https://fusion.actuallab.net/PartTS-React)
|
|
5
|
+
[](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.
|
|
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.
|
|
30
|
-
"@actuallab/fusion": "14.3.
|
|
29
|
+
"@actuallab/core": "14.3.32",
|
|
30
|
+
"@actuallab/fusion": "14.3.32"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"react": "^19.0.0"
|