@xmachines/play-react 2.0.0 → 2.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.
- package/README.md +48 -31
- package/dist/ActorProvider.d.ts +24 -20
- package/dist/ActorProvider.d.ts.map +1 -1
- package/dist/ActorProvider.js +97 -83
- package/dist/ActorProvider.js.map +1 -1
- package/dist/PlayErrorBoundary.d.ts +15 -14
- package/dist/PlayErrorBoundary.d.ts.map +1 -1
- package/dist/PlayErrorBoundary.js +10 -9
- package/dist/PlayErrorBoundary.js.map +1 -1
- package/dist/PlayRenderer.d.ts +10 -9
- package/dist/PlayRenderer.d.ts.map +1 -1
- package/dist/PlayRenderer.js +10 -9
- package/dist/PlayRenderer.js.map +1 -1
- package/dist/PlayUIProvider.d.ts +12 -11
- package/dist/PlayUIProvider.d.ts.map +1 -1
- package/dist/PlayUIProvider.js +19 -15
- package/dist/PlayUIProvider.js.map +1 -1
- package/dist/index.d.ts +19 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -18
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.js +3 -3
- package/dist/useActor.d.ts +7 -5
- package/dist/useActor.d.ts.map +1 -1
- package/dist/useActor.js +4 -4
- package/dist/useActor.js.map +1 -1
- package/dist/useSignalEffect.d.ts +40 -39
- package/dist/useSignalEffect.d.ts.map +1 -1
- package/dist/useSignalEffect.js +61 -58
- package/dist/useSignalEffect.js.map +1 -1
- package/package.json +5 -4
- package/dist/errors.d.ts +0 -23
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -26
- package/dist/errors.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
React renderer for XMachines Play architecture with signal-driven rendering.
|
|
4
4
|
|
|
5
|
-
[](https://opensource.org/licenses/MIT) [.
|
|
5
|
+
[](https://opensource.org/licenses/MIT) [](https://www.npmjs.com/package/@xmachines/play-react)
|
|
8
6
|
|
|
9
7
|
## Installation
|
|
10
8
|
|
|
@@ -12,7 +10,7 @@ Part of the [xmachines-js monorepo](../../README.md).
|
|
|
12
10
|
pnpm add @xmachines/play-react
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
**Peer dependencies
|
|
13
|
+
**Peer dependencies.** Install them separately:
|
|
16
14
|
|
|
17
15
|
```bash
|
|
18
16
|
pnpm add react react-dom xstate @xstate/store @xmachines/json-render-react @xmachines/json-render-core @xmachines/json-render-xstate
|
|
@@ -23,7 +21,7 @@ Supported versions:
|
|
|
23
21
|
- `react` / `react-dom`: `^18.0.0 || ^19.0.0`
|
|
24
22
|
- `xstate`: `^5.31.0`
|
|
25
23
|
- `@xstate/store`: `^3.17.0`
|
|
26
|
-
- `@xmachines/json-render-*`: `^0.
|
|
24
|
+
- `@xmachines/json-render-*`: `^0.20.0-xm.2`
|
|
27
25
|
|
|
28
26
|
## Usage
|
|
29
27
|
|
|
@@ -34,6 +32,9 @@ The recommended pattern for actor-driven React rendering:
|
|
|
34
32
|
```tsx
|
|
35
33
|
import { PlayUIProvider, PlayRenderer, defineRegistry } from "@xmachines/play-react";
|
|
36
34
|
import { definePlayer } from "@xmachines/play-xstate";
|
|
35
|
+
import { myMachine } from "./machine.js"; // your xstate machine (states carry meta.view specs)
|
|
36
|
+
import { myCatalog } from "./catalog.js"; // defineCatalog(schema, ...) result, using the schema from "@xmachines/json-render-react/schema"
|
|
37
|
+
import { Login, Dashboard } from "./components.js"; // your React components
|
|
37
38
|
|
|
38
39
|
// 1. Create and start the actor
|
|
39
40
|
const actor = definePlayer({ machine: myMachine })();
|
|
@@ -63,27 +64,42 @@ function App() {
|
|
|
63
64
|
Pass navigation and validation helpers through `PlayUIProvider`:
|
|
64
65
|
|
|
65
66
|
```tsx
|
|
67
|
+
// actor, registryResult from the Quick Start above
|
|
66
68
|
<PlayUIProvider
|
|
67
69
|
actor={actor}
|
|
68
70
|
registryResult={registryResult}
|
|
69
|
-
navigate={(path) =>
|
|
71
|
+
navigate={(path) => history.pushState(null, "", path)}
|
|
70
72
|
validationFunctions={{ isEmail: (v) => /^.+@.+$/.test(String(v)) }}
|
|
71
73
|
>
|
|
72
74
|
<PlayRenderer />
|
|
73
75
|
</PlayUIProvider>
|
|
74
76
|
```
|
|
75
77
|
|
|
76
|
-
###
|
|
78
|
+
### Custom provider composition
|
|
77
79
|
|
|
78
80
|
Use `ActorProvider` directly when you need to compose providers manually:
|
|
79
81
|
|
|
80
82
|
```tsx
|
|
81
|
-
import {
|
|
83
|
+
import type { ReactNode } from "react";
|
|
84
|
+
import { ActorProvider, JSONUIProvider, PlayRenderer, usePlayView } from "@xmachines/play-react";
|
|
85
|
+
|
|
86
|
+
// Handlers and store live in ViewContext, so an inner bridge component must
|
|
87
|
+
// read them via usePlayView() and forward all three to JSONUIProvider —
|
|
88
|
+
// passing only `registry` would drop the action handlers and create a fresh store.
|
|
89
|
+
function Bridge({ children }: { children: ReactNode }) {
|
|
90
|
+
const view = usePlayView();
|
|
91
|
+
return (
|
|
92
|
+
<JSONUIProvider registry={view.registry} handlers={view.handlers} store={view.store}>
|
|
93
|
+
{children}
|
|
94
|
+
</JSONUIProvider>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
82
97
|
|
|
98
|
+
// actor, registryResult from the Quick Start above
|
|
83
99
|
<ActorProvider actor={actor} registryResult={registryResult}>
|
|
84
|
-
<
|
|
100
|
+
<Bridge>
|
|
85
101
|
<PlayRenderer />
|
|
86
|
-
</
|
|
102
|
+
</Bridge>
|
|
87
103
|
</ActorProvider>;
|
|
88
104
|
```
|
|
89
105
|
|
|
@@ -101,6 +117,7 @@ function SubmitButton() {
|
|
|
101
117
|
### Subscribing to signals directly
|
|
102
118
|
|
|
103
119
|
```tsx
|
|
120
|
+
import { useState } from "react";
|
|
104
121
|
import { useSignalEffect } from "@xmachines/play-react";
|
|
105
122
|
|
|
106
123
|
function MyComponent({ actor }) {
|
|
@@ -110,7 +127,7 @@ function MyComponent({ actor }) {
|
|
|
110
127
|
setView(actor.currentView.get());
|
|
111
128
|
}, [actor]); // deps: re-subscribe when the actor prop swaps
|
|
112
129
|
|
|
113
|
-
return <div>{view?.
|
|
130
|
+
return <div>{view?.root}</div>;
|
|
114
131
|
}
|
|
115
132
|
```
|
|
116
133
|
|
|
@@ -120,34 +137,34 @@ function MyComponent({ actor }) {
|
|
|
120
137
|
|
|
121
138
|
| Export | Description |
|
|
122
139
|
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
123
|
-
| `<PlayUIProvider>` |
|
|
140
|
+
| `<PlayUIProvider>` | Composite provider. It wraps `ActorProvider` and `JSONUIProvider`. Use it as the standard entry point. |
|
|
124
141
|
| `<PlayRenderer>` | Zero-prop leaf component. Reads the current actor view from context and renders it. Must be inside `PlayUIProvider` or `ActorProvider`. |
|
|
125
|
-
| `<ActorProvider>` |
|
|
126
|
-
| `<PlayErrorBoundary>` | React class error boundary
|
|
142
|
+
| `<ActorProvider>` | The low-level provider. It owns the actor bridge, the signal subscription, and the `StateStore` lifecycle of each view. |
|
|
143
|
+
| `<PlayErrorBoundary>` | The React class error boundary that catches a render error of a catalog component. |
|
|
127
144
|
|
|
128
145
|
### Hooks
|
|
129
146
|
|
|
130
|
-
| Export | Description
|
|
131
|
-
| ---------------------------------- |
|
|
132
|
-
| `useSignalEffect(callback, deps?)` | Subscribes to TC39 signal changes
|
|
133
|
-
| `useActor()` | Returns the raw actor instance. Must be called inside an `ActorProvider`/`PlayUIProvider` tree.
|
|
134
|
-
| `usePlayView()` | Returns `{ spec, handlers, registry, store }` for the current view. Must be called inside an `ActorProvider`/`PlayUIProvider` tree.
|
|
147
|
+
| Export | Description |
|
|
148
|
+
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
149
|
+
| `useSignalEffect(callback, deps?)` | Subscribes to the TC39 signal changes. It runs the callback again when a signal that the callback reads changes, and the callback triggers the re-render with its own setState. The optional `deps` array creates the subscription again, like `useEffect`. The hook removes the subscription on unmount. |
|
|
150
|
+
| `useActor()` | Returns the raw actor instance. Must be called inside an `ActorProvider`/`PlayUIProvider` tree. |
|
|
151
|
+
| `usePlayView()` | Returns `{ spec, handlers, registry, store }` for the current view. Must be called inside an `ActorProvider`/`PlayUIProvider` tree. |
|
|
135
152
|
|
|
136
153
|
### Types
|
|
137
154
|
|
|
138
|
-
| Export | Description
|
|
139
|
-
| ------------------------ |
|
|
140
|
-
| `PlayUIProviderProps` | Props for `<PlayUIProvider>`
|
|
141
|
-
| `ActorProviderProps` | Props for `<ActorProvider>` (also exported as `PlayRendererProps` for migration compatibility)
|
|
142
|
-
| `PlayErrorBoundaryProps` | Props for `<PlayErrorBoundary>`
|
|
143
|
-
| `PlayErrorBoundaryState` | State shape for `<PlayErrorBoundary>`
|
|
144
|
-
| `AnyPlayActor` | Type alias for `AbstractActor<AnyActorLogic>` — the bare actor type
|
|
145
|
-
| `ViewContextValue` |
|
|
146
|
-
| `RenderErrorHandler` | Error handler callback type for render errors
|
|
155
|
+
| Export | Description |
|
|
156
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| `PlayUIProviderProps` | Props for `<PlayUIProvider>` |
|
|
158
|
+
| `ActorProviderProps` | Props for `<ActorProvider>` (also exported as `PlayRendererProps` for migration compatibility) |
|
|
159
|
+
| `PlayErrorBoundaryProps` | Props for `<PlayErrorBoundary>` |
|
|
160
|
+
| `PlayErrorBoundaryState` | State shape for `<PlayErrorBoundary>` |
|
|
161
|
+
| `AnyPlayActor` | Type alias for `AbstractActor<AnyActorLogic>` — the bare actor type that the context providers use |
|
|
162
|
+
| `ViewContextValue` | The value shape that `usePlayView()` returns |
|
|
163
|
+
| `RenderErrorHandler` | Error handler callback type for render errors |
|
|
147
164
|
|
|
148
165
|
### Re-exports from `@xmachines/json-render-react`
|
|
149
166
|
|
|
150
|
-
`@xmachines/play-react` re-exports the
|
|
167
|
+
`@xmachines/play-react` re-exports the complete `@xmachines/json-render-react` surface, so a consumer needs one import only:
|
|
151
168
|
|
|
152
169
|
```ts
|
|
153
170
|
import {
|
|
@@ -164,7 +181,7 @@ import {
|
|
|
164
181
|
|
|
165
182
|
## Key Principle
|
|
166
183
|
|
|
167
|
-
React state is **never**
|
|
184
|
+
React state is **never** the place for the business logic. It only triggers the render cycle of React. The signals (`@xmachines/play-signals`) are the source of truth. `PlayUIProvider` observes the actor signals with `useSignalEffect`, and it renders again when the current view changes. It groups rapid signal updates into microtasks, so React does not render more often than necessary.
|
|
168
185
|
|
|
169
186
|
## Testing
|
|
170
187
|
|
|
@@ -180,7 +197,7 @@ Run tests with coverage:
|
|
|
180
197
|
pnpm --filter @xmachines/play-react run test:coverage
|
|
181
198
|
```
|
|
182
199
|
|
|
183
|
-
Run browser integration tests
|
|
200
|
+
Run the browser integration tests. They require Chromium:
|
|
184
201
|
|
|
185
202
|
```bash
|
|
186
203
|
pnpm --filter @xmachines/play-react run test:browser
|
package/dist/ActorProvider.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ActorProvider —
|
|
2
|
+
* ActorProvider — the low-level provider for the actor lifecycle.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* lifecycle (controlled
|
|
6
|
-
* (uses useStateStore()),
|
|
4
|
+
* It owns the actor bridge, the signal subscription (useSignalEffect), the
|
|
5
|
+
* StateStore lifecycle of each view (controlled and uncontrolled), the handler
|
|
6
|
+
* resolution through the inner component pattern (it uses useStateStore()), the
|
|
7
|
+
* StateProvider wrapper, the PlayErrorBoundary wrapper, and the injection of
|
|
8
|
+
* onRenderError.
|
|
7
9
|
*
|
|
8
|
-
* Standard
|
|
10
|
+
* Standard use: prefer <PlayUIProvider>, and use this component only when you
|
|
11
|
+
* compose the providers yourself.
|
|
9
12
|
*
|
|
10
13
|
* @packageDocumentation
|
|
11
14
|
*/
|
|
@@ -13,31 +16,31 @@ import React from "react";
|
|
|
13
16
|
import type { DefineRegistryResult, ComponentRegistry } from "@xmachines/json-render-react";
|
|
14
17
|
import { type BaseActorProviderProps, type BaseViewContextValue } from "@xmachines/play-actor";
|
|
15
18
|
/**
|
|
16
|
-
*
|
|
19
|
+
* The props of the ActorProvider component.
|
|
17
20
|
*
|
|
18
21
|
* @public
|
|
19
22
|
*/
|
|
20
23
|
export interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> {
|
|
21
|
-
/**
|
|
24
|
+
/** The component to show when currentView is null, or when a catalog component throws. This prop is optional */
|
|
22
25
|
fallback?: React.ReactNode;
|
|
23
|
-
/**
|
|
26
|
+
/** The optional error handler. The provider calls it when a catalog component throws during a render */
|
|
24
27
|
onError?: (error: Error, info: React.ErrorInfo) => void;
|
|
25
|
-
/**
|
|
28
|
+
/** The child components to render inside the provider tree */
|
|
26
29
|
children: React.ReactNode;
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
29
|
-
*
|
|
32
|
+
* The value that ViewContext provides. usePlayView() reads it.
|
|
30
33
|
*
|
|
31
34
|
* @public
|
|
32
35
|
*/
|
|
33
36
|
export interface ViewContextValue extends BaseViewContextValue<ComponentRegistry> {
|
|
34
37
|
}
|
|
35
38
|
/**
|
|
36
|
-
*
|
|
39
|
+
* The hook that gives the current view spec, the handlers, and the registry.
|
|
37
40
|
*
|
|
38
|
-
*
|
|
41
|
+
* Call it inside <ActorProvider> or <PlayUIProvider>.
|
|
39
42
|
*
|
|
40
|
-
* @throws {Error}
|
|
43
|
+
* @throws {Error} When the caller is outside an ActorProvider or PlayUIProvider tree
|
|
41
44
|
*
|
|
42
45
|
* @example
|
|
43
46
|
* ```typescript
|
|
@@ -53,24 +56,25 @@ export interface ViewContextValue extends BaseViewContextValue<ComponentRegistry
|
|
|
53
56
|
*/
|
|
54
57
|
export declare function usePlayView(): ViewContextValue;
|
|
55
58
|
/**
|
|
56
|
-
* ActorProvider —
|
|
59
|
+
* ActorProvider — the low-level provider that composes the actor lifecycle with your own providers.
|
|
57
60
|
*
|
|
58
|
-
*
|
|
59
|
-
* wraps children in StateProvider and
|
|
60
|
-
* into the component registry.
|
|
61
|
+
* It subscribes to the actor.currentView signal. It manages the StateStore
|
|
62
|
+
* lifecycle of each view. It wraps the children in StateProvider and in
|
|
63
|
+
* PlayErrorBoundary. It also puts onRenderError into the component registry.
|
|
61
64
|
*
|
|
62
|
-
* Standard
|
|
65
|
+
* Standard use: prefer <PlayUIProvider>, and use this component only when you
|
|
66
|
+
* compose the providers yourself.
|
|
63
67
|
*
|
|
64
68
|
* @example
|
|
65
69
|
* ```tsx
|
|
66
|
-
* //
|
|
70
|
+
* // A custom composition:
|
|
67
71
|
* <ActorProvider actor={actor} registryResult={registryResult}>
|
|
68
72
|
* <JSONUIProvider registry={registryResult.registry}>
|
|
69
73
|
* <PlayRenderer />
|
|
70
74
|
* </JSONUIProvider>
|
|
71
75
|
* </ActorProvider>
|
|
72
76
|
*
|
|
73
|
-
* // Standard
|
|
77
|
+
* // Standard use: prefer PlayUIProvider
|
|
74
78
|
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
75
79
|
* <PlayRenderer />
|
|
76
80
|
* </PlayUIProvider>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActorProvider.d.ts","sourceRoot":"","sources":["../src/ActorProvider.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ActorProvider.d.ts","sourceRoot":"","sources":["../src/ActorProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EACX,oBAAoB,EAEpB,iBAAiB,EACjB,MAAM,8BAA8B,CAAC;AAOtC,OAAO,EAMN,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,MAAM,uBAAuB,CAAC;AAI/B;;;;GAIG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB,CAAC,oBAAoB,CAAC;IACvF,gHAAgH;IAChH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,wGAAwG;IACxG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;IACxD,8DAA8D;IAC9D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;CAAG;AAQpF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,IAAI,gBAAgB,CAE9C;AA+ED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqHtD,CAAC"}
|
package/dist/ActorProvider.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* ActorProvider —
|
|
3
|
+
* ActorProvider — the low-level provider for the actor lifecycle.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* lifecycle (controlled
|
|
7
|
-
* (uses useStateStore()),
|
|
5
|
+
* It owns the actor bridge, the signal subscription (useSignalEffect), the
|
|
6
|
+
* StateStore lifecycle of each view (controlled and uncontrolled), the handler
|
|
7
|
+
* resolution through the inner component pattern (it uses useStateStore()), the
|
|
8
|
+
* StateProvider wrapper, the PlayErrorBoundary wrapper, and the injection of
|
|
9
|
+
* onRenderError.
|
|
8
10
|
*
|
|
9
|
-
* Standard
|
|
11
|
+
* Standard use: prefer <PlayUIProvider>, and use this component only when you
|
|
12
|
+
* compose the providers yourself.
|
|
10
13
|
*
|
|
11
14
|
* @packageDocumentation
|
|
12
15
|
*/
|
|
@@ -20,16 +23,16 @@ import { assertNonNullable } from "@xmachines/play";
|
|
|
20
23
|
import { attachRenderErrorHandler, createViewStoreLifecycle, refreshContextSubtree, } from "@xmachines/play-actor";
|
|
21
24
|
import { ActorContext } from "./useActor.js";
|
|
22
25
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
26
|
+
* The internal React context of ViewContextValue.
|
|
27
|
+
* The usePlayView() hook reads it.
|
|
25
28
|
*/
|
|
26
29
|
const ViewContext = createContext(null);
|
|
27
30
|
/**
|
|
28
|
-
*
|
|
31
|
+
* The hook that gives the current view spec, the handlers, and the registry.
|
|
29
32
|
*
|
|
30
|
-
*
|
|
33
|
+
* Call it inside <ActorProvider> or <PlayUIProvider>.
|
|
31
34
|
*
|
|
32
|
-
* @throws {Error}
|
|
35
|
+
* @throws {Error} When the caller is outside an ActorProvider or PlayUIProvider tree
|
|
33
36
|
*
|
|
34
37
|
* @example
|
|
35
38
|
* ```typescript
|
|
@@ -47,42 +50,47 @@ export function usePlayView() {
|
|
|
47
50
|
return assertNonNullable(useContext(ViewContext), "ViewContext");
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
53
|
+
* Creates a StateStore on a new @xstate/store atom, with the given state as its
|
|
54
|
+
* first value. The provider calls this function for each view transition, when no
|
|
55
|
+
* external store prop is present.
|
|
52
56
|
*/
|
|
53
57
|
function createViewStore(initialState) {
|
|
54
58
|
return xstateStoreStateStore({ atom: createAtom(initialState) });
|
|
55
59
|
}
|
|
56
60
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* the live
|
|
61
|
+
* The inner component runs inside StateProvider, so that it can read the StateStore
|
|
62
|
+
* context with useStateStore(). It resolves each action handler from
|
|
63
|
+
* registryResult.handlers(), with the live set and getSnapshot functions of
|
|
64
|
+
* StateProvider. It then puts the handlers in ViewContext.
|
|
60
65
|
*/
|
|
61
66
|
function ActorProviderInner({ registryResult, spec, store, children, }) {
|
|
62
67
|
const stateCtx = useStateStore();
|
|
63
|
-
// Stable refs
|
|
64
|
-
// on
|
|
65
|
-
// when the
|
|
66
|
-
// getter functions and calls them at
|
|
67
|
-
//
|
|
68
|
+
// Stable refs of the stateCtx methods. The useMemo below therefore needs no
|
|
69
|
+
// dependency on the identity of stateCtx, because useStateStore() can return a new
|
|
70
|
+
// object on each render, also when the store below it did not change. The factory of
|
|
71
|
+
// the handlers gives these refs as getter functions, and it calls them at the moment
|
|
72
|
+
// of an action, and not at the moment of the creation. A read from a ref is therefore
|
|
73
|
+
// always correct.
|
|
68
74
|
const stateCtxRef = useRef(stateCtx);
|
|
69
75
|
stateCtxRef.current = stateCtx;
|
|
70
|
-
// Build a SetState adapter: the handlers
|
|
71
|
-
// (
|
|
72
|
-
//
|
|
73
|
-
//
|
|
76
|
+
// Build a SetState adapter: the factory of the handlers expects the pattern of an
|
|
77
|
+
// updater function, `(prev) => next`, and stateCtx gives a set function and an update
|
|
78
|
+
// function on a path. This adapter joins the two. An action function can therefore
|
|
79
|
+
// use setState.
|
|
80
|
+
// The function reference is stable, and the function reads stateCtxRef.current at the
|
|
81
|
+
// moment of the call.
|
|
74
82
|
const setStateAdapterRef = useRef((updater) => {
|
|
75
83
|
const prev = stateCtxRef.current.getSnapshot();
|
|
76
84
|
stateCtxRef.current.update(updater(prev));
|
|
77
85
|
});
|
|
78
|
-
//
|
|
79
|
-
// stable refs
|
|
80
|
-
//
|
|
81
|
-
// call
|
|
86
|
+
// Keep the handlers, with the identity of registryResult as the key. The getter
|
|
87
|
+
// functions are stable refs, and they therefore cause no new computation. The code
|
|
88
|
+
// makes the handlers again only after a change of the definition of the registry, for
|
|
89
|
+
// example after a new defineRegistry call, and not on each render.
|
|
82
90
|
const handlers = useMemo(() => registryResult.handlers(() => setStateAdapterRef.current, () => stateCtxRef.current.getSnapshot()), [registryResult]);
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
91
|
+
// Keep the value of the context on its real inputs. A new object on each render
|
|
92
|
+
// renders every usePlayView() consumer again, also when nothing changed, and those
|
|
93
|
+
// renders are lost work.
|
|
86
94
|
const viewValue = useMemo(() => ({
|
|
87
95
|
spec,
|
|
88
96
|
handlers,
|
|
@@ -92,24 +100,25 @@ function ActorProviderInner({ registryResult, spec, store, children, }) {
|
|
|
92
100
|
return _jsx(ViewContext.Provider, { value: viewValue, children: children });
|
|
93
101
|
}
|
|
94
102
|
/**
|
|
95
|
-
* ActorProvider —
|
|
103
|
+
* ActorProvider — the low-level provider that composes the actor lifecycle with your own providers.
|
|
96
104
|
*
|
|
97
|
-
*
|
|
98
|
-
* wraps children in StateProvider and
|
|
99
|
-
* into the component registry.
|
|
105
|
+
* It subscribes to the actor.currentView signal. It manages the StateStore
|
|
106
|
+
* lifecycle of each view. It wraps the children in StateProvider and in
|
|
107
|
+
* PlayErrorBoundary. It also puts onRenderError into the component registry.
|
|
100
108
|
*
|
|
101
|
-
* Standard
|
|
109
|
+
* Standard use: prefer <PlayUIProvider>, and use this component only when you
|
|
110
|
+
* compose the providers yourself.
|
|
102
111
|
*
|
|
103
112
|
* @example
|
|
104
113
|
* ```tsx
|
|
105
|
-
* //
|
|
114
|
+
* // A custom composition:
|
|
106
115
|
* <ActorProvider actor={actor} registryResult={registryResult}>
|
|
107
116
|
* <JSONUIProvider registry={registryResult.registry}>
|
|
108
117
|
* <PlayRenderer />
|
|
109
118
|
* </JSONUIProvider>
|
|
110
119
|
* </ActorProvider>
|
|
111
120
|
*
|
|
112
|
-
* // Standard
|
|
121
|
+
* // Standard use: prefer PlayUIProvider
|
|
113
122
|
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
114
123
|
* <PlayRenderer />
|
|
115
124
|
* </PlayUIProvider>
|
|
@@ -118,45 +127,47 @@ function ActorProviderInner({ registryResult, spec, store, children, }) {
|
|
|
118
127
|
* @public
|
|
119
128
|
*/
|
|
120
129
|
export const ActorProvider = ({ actor, registryResult, store: externalStore, fallback = null, onError, onRenderError, children, }) => {
|
|
121
|
-
// React state for
|
|
122
|
-
//
|
|
130
|
+
// React state, for the trigger of a new render. It is NOT the state of the business
|
|
131
|
+
// logic. The signal is the source of truth, and useState is the render trigger of
|
|
132
|
+
// React only.
|
|
123
133
|
const [view, setView] = useState(() => actor.currentView.get());
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
134
|
+
// The store lifecycle: it seeds the store again on a change of the viewKey, it
|
|
135
|
+
// refreshes /context in place in every other case, it resets the store on a change of
|
|
136
|
+
// the actor, and it guards the identity cache. The shared coordinator comes from
|
|
137
|
+
// @xmachines/play-actor. Only the wiring of the reactivity, which is the render path
|
|
138
|
+
// and the effects below, belongs to React.
|
|
128
139
|
const storeLifecycleRef = useRef(null);
|
|
129
140
|
storeLifecycleRef.current ??= createViewStoreLifecycle(createViewStore);
|
|
130
141
|
const storeLifecycle = storeLifecycleRef.current;
|
|
131
|
-
//
|
|
132
|
-
// [actor]
|
|
142
|
+
// The newest external store, for the signal effect below. The deps of that effect are
|
|
143
|
+
// [actor]. Therefore the effect must hold no prop that can be old.
|
|
133
144
|
const externalStoreRef = useRef(externalStore);
|
|
134
145
|
externalStoreRef.current = externalStore;
|
|
135
|
-
// Subscribe to signal changes. The [actor]
|
|
136
|
-
// the actor prop
|
|
137
|
-
//
|
|
138
|
-
//
|
|
146
|
+
// Subscribe to the signal changes. The [actor] dependency makes the watcher again
|
|
147
|
+
// when the actor prop changes. Without it, the watcher tracks the currentView signal
|
|
148
|
+
// of the OLD actor, and the view on the screen freezes on the old actor while the
|
|
149
|
+
// events go to the new one.
|
|
139
150
|
//
|
|
140
|
-
//
|
|
141
|
-
// store and the spec the children
|
|
142
|
-
//
|
|
151
|
+
// The store update happens HERE, before setView starts the new render. Therefore the
|
|
152
|
+
// store and the spec of the children are always a consistent pair, and the store
|
|
153
|
+
// notifies its subscribers never during a render pass.
|
|
143
154
|
useSignalEffect(() => {
|
|
144
155
|
const currentView = actor.currentView.get();
|
|
145
156
|
if (currentView) {
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
157
|
+
// The controlled mode is here too: the machinery owns /context in both modes, and a
|
|
158
|
+
// refresh of the external store is permitted here, because the effect runs after the
|
|
159
|
+
// commit, and never during a render pass.
|
|
149
160
|
storeLifecycle.resolve(actor, currentView, externalStoreRef.current);
|
|
150
161
|
}
|
|
151
162
|
setView(currentView);
|
|
152
163
|
}, [actor]);
|
|
153
|
-
//
|
|
154
|
-
// render path
|
|
155
|
-
// must
|
|
156
|
-
// effect above runs after paint
|
|
157
|
-
//
|
|
158
|
-
// prop
|
|
159
|
-
//
|
|
164
|
+
// The controlled mode: refresh the /context of the external store BEFORE the paint.
|
|
165
|
+
// The render path gives the children the external store without a change, because the
|
|
166
|
+
// machinery must notify no store subscriber during a render, and the passive signal
|
|
167
|
+
// effect above runs after the paint. This layout effect closes the gap of the first
|
|
168
|
+
// paint. Its key also holds the identity of the store. Therefore a new controlled
|
|
169
|
+
// store prop during a session brings the NEW store in line at once, and it waits not
|
|
170
|
+
// for the next emission of a view.
|
|
160
171
|
useLayoutEffect(() => {
|
|
161
172
|
if (!externalStore)
|
|
162
173
|
return;
|
|
@@ -164,11 +175,13 @@ export const ActorProvider = ({ actor, registryResult, store: externalStore, fal
|
|
|
164
175
|
if (currentView)
|
|
165
176
|
refreshContextSubtree(externalStore, currentView);
|
|
166
177
|
}, [externalStore, actor]);
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
178
|
+
// Put the onRenderError prop into the registry. The property is not enumerable, and
|
|
179
|
+
// it replaces the handler of the defineRegistry level.
|
|
180
|
+
// D-19 puts this work here: one place for every framework renderer.
|
|
181
|
+
// The code keeps the result on its real inputs. A new injected registry on each render
|
|
182
|
+
// churns the identity of the registry, and it therefore invalidates the handlers
|
|
183
|
+
// useMemo of ActorProviderInner on each render. That is lost work, and each consumer
|
|
184
|
+
// also renders again for nothing.
|
|
172
185
|
const activeRegistryResult = useMemo(() => {
|
|
173
186
|
if (!onRenderError)
|
|
174
187
|
return registryResult;
|
|
@@ -177,25 +190,26 @@ export const ActorProvider = ({ actor, registryResult, store: externalStore, fal
|
|
|
177
190
|
registry: attachRenderErrorHandler(registryResult.registry, onRenderError),
|
|
178
191
|
};
|
|
179
192
|
}, [registryResult, onRenderError]);
|
|
180
|
-
// Resolve the store
|
|
181
|
-
// the
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
// through
|
|
189
|
-
//
|
|
193
|
+
// Resolve the store of StateProvider on the render path too. This covers the first
|
|
194
|
+
// render, where the initializer of useState seeded `view` before an effect ran, and it
|
|
195
|
+
// does nothing after that: a new seed branches on the viewKey, and the /context
|
|
196
|
+
// refresh compares the values before a write. Therefore the store notifies no
|
|
197
|
+
// subscriber during a render. The refresh of the EXTERNAL store waits here, for the
|
|
198
|
+
// same reason of the render, and the layout effect above does it.
|
|
199
|
+
// The children receive the store with the guard: /context is read-only to the spec,
|
|
200
|
+
// which includes $bindState, setState, and a chained set, because the machine context
|
|
201
|
+
// changes through an event only. The coordinator keeps the identity of the guard for
|
|
202
|
+
// each store below it. Therefore each consumer stays stable by its reference.
|
|
190
203
|
const guardedStore = view
|
|
191
204
|
? storeLifecycle.resolve(actor, view, externalStore, { refreshExternalStore: false })
|
|
192
205
|
.guardedStore
|
|
193
206
|
: null;
|
|
194
|
-
//
|
|
195
|
-
// fallback component can call useActor()
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
207
|
+
// The current state has no view: render the fallback INSIDE ActorContext, so that a
|
|
208
|
+
// fallback component can call useActor(), for example to send an event of a retry.
|
|
209
|
+
// The fallback of the error boundary below does the same, and so do the renderers of
|
|
210
|
+
// Solid, of Svelte, and of Vue: each of them gives the context to its fallback of a
|
|
211
|
+
// null view.
|
|
212
|
+
// The code gives ViewContext deliberately NOT: there is no view spec to give.
|
|
199
213
|
if (!view || !guardedStore) {
|
|
200
214
|
return (_jsx(ActorContext.Provider, { value: actor, children: fallback }));
|
|
201
215
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActorProvider.js","sourceRoot":"","sources":["../src/ActorProvider.tsx"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"ActorProvider.js","sourceRoot":"","sources":["../src/ActorProvider.tsx"],"names":[],"mappings":"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"}
|