@xmachines/play-react 1.0.0-beta.8 → 1.0.0-beta.9

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
@@ -28,12 +28,14 @@ npm install @xmachines/play-react
28
28
 
29
29
  - `PlayRenderer`
30
30
  - `useSignalEffect`
31
+ - `PlayErrorBoundary`
31
32
  - `PlayRendererProps` (type)
33
+ - `PlayErrorBoundaryProps` (type)
32
34
 
33
35
  **Peer dependencies:**
34
36
 
35
- - `react` ^18.0.0 - React runtime
36
- - `react-dom` ^18.0.0 - React DOM renderer
37
+ - `react` ^18.0.0 || ^19.0.0 — React runtime
38
+ - `react-dom` ^18.0.0 || ^19.0.0 — React DOM renderer
37
39
 
38
40
  ## Quick Start
39
41
 
@@ -175,6 +177,40 @@ function RouteDisplay({ actor }: { actor: AbstractActor<any> }) {
175
177
  }
176
178
  ```
177
179
 
180
+ ### PlayErrorBoundary
181
+
182
+ React class component error boundary for catching catalog component render errors.
183
+
184
+ `PlayRenderer` wraps its render output in `PlayErrorBoundary` automatically. You can also use it directly to wrap any component that may throw during render.
185
+
186
+ ```typescript
187
+ interface PlayErrorBoundaryProps {
188
+ fallback?: React.ReactNode; // UI shown when a child throws (default: null)
189
+ children: React.ReactNode;
190
+ onError?: (error: Error, info: React.ErrorInfo) => void; // Forward to Sentry, Datadog, etc.
191
+ }
192
+ ```
193
+
194
+ **Props:**
195
+
196
+ - `fallback` — ReactNode rendered when a child component throws. Defaults to `null`.
197
+ - `onError` — Optional callback forwarded on every caught error. Use for production observability (Sentry, Datadog, custom logging).
198
+
199
+ **Example:**
200
+
201
+ ```tsx
202
+ import { PlayErrorBoundary } from "@xmachines/play-react";
203
+
204
+ <PlayErrorBoundary
205
+ fallback={<div className="error">Something went wrong.</div>}
206
+ onError={(error) => Sentry.captureException(error)}
207
+ >
208
+ <YourCatalogComponent />
209
+ </PlayErrorBoundary>;
210
+ ```
211
+
212
+ Works with React 18 and React 19. Uses the standard class component `componentDidCatch` + `getDerivedStateFromError` pattern.
213
+
178
214
  **Complete API:** See [API Documentation](../../docs/api/@xmachines/play-react)
179
215
 
180
216
  ## Examples
@@ -0,0 +1,47 @@
1
+ /**
2
+ * PlayErrorBoundary - React error boundary for catching catalog component render errors
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ import React from "react";
7
+ /**
8
+ * Props for PlayErrorBoundary
9
+ *
10
+ * @public
11
+ */
12
+ export interface PlayErrorBoundaryProps {
13
+ /** Fallback UI to render when a child component throws. Defaults to null. */
14
+ fallback?: React.ReactNode;
15
+ /** Child components to render */
16
+ children: React.ReactNode;
17
+ /** Optional error handler callback — forwards errors to observability tools (Sentry, etc.) */
18
+ onError?: (error: Error, info: React.ErrorInfo) => void;
19
+ }
20
+ interface PlayErrorBoundaryState {
21
+ hasError: boolean;
22
+ error: Error | null;
23
+ }
24
+ /**
25
+ * React class component error boundary for catching catalog component render errors.
26
+ *
27
+ * Wraps catalog component renders so failures are caught and forwarded to standard
28
+ * React error boundary protocol. Consumers can attach the `onError` prop to forward
29
+ * errors to production observability tools (Sentry, Datadog, etc.).
30
+ *
31
+ * Per CONS-14: Class component pattern works with all React versions (18 and 19).
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * <PlayErrorBoundary fallback={<div>Something went wrong</div>} onError={Sentry.captureException}>
36
+ * <CatalogComponent {...props} />
37
+ * </PlayErrorBoundary>
38
+ * ```
39
+ */
40
+ export declare class PlayErrorBoundary extends React.Component<PlayErrorBoundaryProps, PlayErrorBoundaryState> {
41
+ constructor(props: PlayErrorBoundaryProps);
42
+ static getDerivedStateFromError(error: Error): PlayErrorBoundaryState;
43
+ componentDidCatch(error: Error, info: React.ErrorInfo): void;
44
+ render(): React.ReactNode;
45
+ }
46
+ export {};
47
+ //# sourceMappingURL=PlayErrorBoundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlayErrorBoundary.d.ts","sourceRoot":"","sources":["../src/PlayErrorBoundary.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,iCAAiC;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,8FAA8F;IAC9F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;CACxD;AAED,UAAU,sBAAsB;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CACrD,sBAAsB,EACtB,sBAAsB,CACtB;gBACY,KAAK,EAAE,sBAAsB;IAKzC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,sBAAsB;IAI5D,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,GAAG,IAAI;IAK5D,MAAM,IAAI,KAAK,CAAC,SAAS;CAMlC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * PlayErrorBoundary - React error boundary for catching catalog component render errors
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ import React from "react";
7
+ /**
8
+ * React class component error boundary for catching catalog component render errors.
9
+ *
10
+ * Wraps catalog component renders so failures are caught and forwarded to standard
11
+ * React error boundary protocol. Consumers can attach the `onError` prop to forward
12
+ * errors to production observability tools (Sentry, Datadog, etc.).
13
+ *
14
+ * Per CONS-14: Class component pattern works with all React versions (18 and 19).
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * <PlayErrorBoundary fallback={<div>Something went wrong</div>} onError={Sentry.captureException}>
19
+ * <CatalogComponent {...props} />
20
+ * </PlayErrorBoundary>
21
+ * ```
22
+ */
23
+ export class PlayErrorBoundary extends React.Component {
24
+ constructor(props) {
25
+ super(props);
26
+ this.state = { hasError: false, error: null };
27
+ }
28
+ static getDerivedStateFromError(error) {
29
+ return { hasError: true, error };
30
+ }
31
+ componentDidCatch(error, info) {
32
+ console.error("[PlayErrorBoundary] Component render error:", error, info);
33
+ this.props.onError?.(error, info);
34
+ }
35
+ render() {
36
+ if (this.state.hasError) {
37
+ return this.props.fallback ?? null;
38
+ }
39
+ return this.props.children;
40
+ }
41
+ }
42
+ //# sourceMappingURL=PlayErrorBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlayErrorBoundary.js","sourceRoot":"","sources":["../src/PlayErrorBoundary.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAqB1B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK,CAAC,SAG5C;IACA,YAAY,KAA6B;QACxC,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC/C,CAAC;IAED,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC3C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAClC,CAAC;IAEQ,iBAAiB,CAAC,KAAY,EAAE,IAAqB;QAC7D,OAAO,CAAC,KAAK,CAAC,6CAA6C,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1E,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAEQ,MAAM;QACd,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC;QACpC,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC5B,CAAC;CACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA2CpD,CAAC"}
1
+ {"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA+CpD,CAAC"}
@@ -6,6 +6,7 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
6
6
  */
7
7
  import React, { useState } from "react";
8
8
  import { useSignalEffect } from "./useSignalEffect.js";
9
+ import { PlayErrorBoundary } from "./PlayErrorBoundary.js";
9
10
  /**
10
11
  * Main renderer component that subscribes to actor signals and renders UI
11
12
  *
@@ -82,6 +83,6 @@ export const PlayRenderer = ({ actor, components, fallback = null, }) => {
82
83
  }
83
84
  // Render with props from actor + send function
84
85
  // bind(actor) ensures 'this' context is correct when components call send()
85
- return _jsx(Component, { ...view.props, send: actor.send.bind(actor) });
86
+ return (_jsx(PlayErrorBoundary, { fallback: fallback, children: _jsx(Component, { ...view.props, send: actor.send.bind(actor) }) }));
86
87
  };
87
88
  //# sourceMappingURL=PlayRenderer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlayRenderer.js","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAIvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,CAAC,MAAM,YAAY,GAAgC,CAAC,EACzD,KAAK,EACL,UAAU,EACV,QAAQ,GAAG,IAAI,GACf,EAAE,EAAE;IACJ,mEAAmE;IACnE,qEAAqE;IACrE,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAsB,GAAG,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC;IAErF,8BAA8B;IAC9B,eAAe,CAAC,GAAG,EAAE;QACpB,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC;QAC5C,OAAO,CAAC,WAAW,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,2BAA2B;IAC3B,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACxB,CAAC;IAED,sDAAsD;IACtD,IAAI,CAAC,UAAU,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CACZ,yBAAyB,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,IAAI;YACtE,4BAA4B,IAAI,CAAC,SAAS,IAAI,CAC/C,CAAC;QACF,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACxB,CAAC;IAED,iCAAiC;IACjC,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE7C,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CACZ,cAAc,IAAI,CAAC,SAAS,0BAA0B;YACrD,yBAAyB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9D,CAAC;QACF,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACxB,CAAC;IAED,+CAA+C;IAC/C,4EAA4E;IAC5E,OAAO,KAAC,SAAS,OAAK,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAI,CAAC;AACpE,CAAC,CAAC"}
1
+ {"version":3,"file":"PlayRenderer.js","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAI3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,CAAC,MAAM,YAAY,GAAgC,CAAC,EACzD,KAAK,EACL,UAAU,EACV,QAAQ,GAAG,IAAI,GACf,EAAE,EAAE;IACJ,mEAAmE;IACnE,qEAAqE;IACrE,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAsB,GAAG,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC;IAErF,8BAA8B;IAC9B,eAAe,CAAC,GAAG,EAAE;QACpB,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC;QAC5C,OAAO,CAAC,WAAW,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,2BAA2B;IAC3B,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACxB,CAAC;IAED,sDAAsD;IACtD,IAAI,CAAC,UAAU,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CACZ,yBAAyB,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,IAAI;YACtE,4BAA4B,IAAI,CAAC,SAAS,IAAI,CAC/C,CAAC;QACF,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACxB,CAAC;IAED,iCAAiC;IACjC,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE7C,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CACZ,cAAc,IAAI,CAAC,SAAS,0BAA0B;YACrD,yBAAyB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9D,CAAC;QACF,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACxB,CAAC;IAED,+CAA+C;IAC/C,4EAA4E;IAC5E,OAAO,CACN,KAAC,iBAAiB,IAAC,QAAQ,EAAE,QAAQ,YACpC,KAAC,SAAS,OAAK,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAI,GACxC,CACpB,CAAC;AACH,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -14,5 +14,7 @@
14
14
  */
15
15
  export { PlayRenderer } from "./PlayRenderer.js";
16
16
  export { useSignalEffect } from "./useSignalEffect.js";
17
+ export { PlayErrorBoundary } from "./PlayErrorBoundary.js";
17
18
  export type { PlayRendererProps } from "./types.js";
19
+ export type { PlayErrorBoundaryProps } from "./PlayErrorBoundary.js";
18
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -15,4 +15,5 @@
15
15
  // Main exports
16
16
  export { PlayRenderer } from "./PlayRenderer.js";
17
17
  export { useSignalEffect } from "./useSignalEffect.js";
18
+ export { PlayErrorBoundary } from "./PlayErrorBoundary.js";
18
19
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAe;AACf,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAe;AACf,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useSignalEffect.d.ts","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,KAAG,IAuDrE,CAAC"}
1
+ {"version":3,"file":"useSignalEffect.d.ts","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,KAAG,IA6DrE,CAAC"}
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @packageDocumentation
5
5
  */
6
- import { useEffect, useReducer } from "react";
6
+ import { useEffect, useReducer, useRef } from "react";
7
7
  import { Signal } from "@xmachines/play-signals";
8
8
  /**
9
9
  * Marker symbol returned by effect Computed to satisfy type requirements
@@ -60,6 +60,11 @@ const EFFECT_RUN_MARKER = Symbol("effect-run");
60
60
  * The Computed handles dependency tracking, and the Watcher monitors it.
61
61
  */
62
62
  export const useSignalEffect = (callback) => {
63
+ // Store callback in a ref so the effect closure always calls the latest version
64
+ // WITHOUT triggering watcher teardown/re-setup on each render (standard React ref pattern)
65
+ const callbackRef = useRef(callback);
66
+ // Assignment outside useEffect keeps ref current without triggering re-effect
67
+ callbackRef.current = callback;
63
68
  // Force re-render when signal changes (React needs state change to re-render)
64
69
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
65
70
  useEffect(() => {
@@ -69,9 +74,10 @@ export const useSignalEffect = (callback) => {
69
74
  // The Computed will re-evaluate when any accessed signal changes
70
75
  const effect = new Signal.Computed(() => {
71
76
  // Run cleanup from previous effect
72
- cleanup?.();
73
- // Run user callback (may access signals, return cleanup)
74
- cleanup = callback();
77
+ if (typeof cleanup === "function")
78
+ cleanup();
79
+ // Run user callback via ref (always latest version, no dep churn)
80
+ cleanup = callbackRef.current();
75
81
  // Return marker value (Computed requires a return value)
76
82
  return EFFECT_RUN_MARKER;
77
83
  });
@@ -98,10 +104,11 @@ export const useSignalEffect = (callback) => {
98
104
  effect.get();
99
105
  // Cleanup on unmount
100
106
  return () => {
101
- cleanup?.();
107
+ if (typeof cleanup === "function")
108
+ cleanup();
102
109
  // Unwatch to stop receiving signal updates
103
110
  watcher.unwatch(effect);
104
111
  };
105
- }, [callback]);
112
+ }, []); // ← [] dep array: watcher created ONCE per mount, not once per render
106
113
  };
107
114
  //# sourceMappingURL=useSignalEffect.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSignalEffect.js","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD;;;GAGG;AACH,MAAM,iBAAiB,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAmC,EAAQ,EAAE;IAC5E,8EAA8E;IAC9E,MAAM,CAAC,EAAE,WAAW,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,OAA4B,CAAC;QACjC,IAAI,YAAY,GAAG,IAAI,CAAC;QAExB,kEAAkE;QAClE,iEAAiE;QACjE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;YACvC,mCAAmC;YACnC,OAAO,EAAE,EAAE,CAAC;YAEZ,yDAAyD;YACzD,OAAO,GAAG,QAAQ,EAAE,CAAC;YAErB,yDAAyD;YACzD,OAAO,iBAAiB,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEH,6DAA6D;QAC7D,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9C,+DAA+D;YAC/D,IAAI,YAAY,EAAE,CAAC;gBAClB,YAAY,GAAG,KAAK,CAAC;gBACrB,gEAAgE;gBAChE,cAAc,CAAC,GAAG,EAAE;oBACnB,YAAY,GAAG,IAAI,CAAC;oBAEpB,kEAAkE;oBAClE,MAAM,CAAC,GAAG,EAAE,CAAC;oBAEb,wBAAwB;oBACxB,WAAW,EAAE,CAAC;oBAEd,2BAA2B;oBAC3B,OAAO,CAAC,KAAK,EAAE,CAAC;gBACjB,CAAC,CAAC,CAAC;YACJ,CAAC;QACF,CAAC,CAAC,CAAC;QAEH,4BAA4B;QAC5B,OAAO,CAAC,KAAK,CAAC,MAAkC,CAAC,CAAC;QAElD,yCAAyC;QACzC,MAAM,CAAC,GAAG,EAAE,CAAC;QAEb,qBAAqB;QACrB,OAAO,GAAG,EAAE;YACX,OAAO,EAAE,EAAE,CAAC;YACZ,2CAA2C;YAC3C,OAAO,CAAC,OAAO,CAAC,MAAkC,CAAC,CAAC;QACrD,CAAC,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChB,CAAC,CAAC"}
1
+ {"version":3,"file":"useSignalEffect.js","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD;;;GAGG;AACH,MAAM,iBAAiB,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAmC,EAAQ,EAAE;IAC5E,gFAAgF;IAChF,2FAA2F;IAC3F,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrC,8EAA8E;IAC9E,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;IAE/B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,WAAW,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,OAA4B,CAAC;QACjC,IAAI,YAAY,GAAG,IAAI,CAAC;QAExB,kEAAkE;QAClE,iEAAiE;QACjE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;YACvC,mCAAmC;YACnC,IAAI,OAAO,OAAO,KAAK,UAAU;gBAAE,OAAO,EAAE,CAAC;YAE7C,kEAAkE;YAClE,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC;YAEhC,yDAAyD;YACzD,OAAO,iBAAiB,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEH,6DAA6D;QAC7D,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9C,+DAA+D;YAC/D,IAAI,YAAY,EAAE,CAAC;gBAClB,YAAY,GAAG,KAAK,CAAC;gBACrB,gEAAgE;gBAChE,cAAc,CAAC,GAAG,EAAE;oBACnB,YAAY,GAAG,IAAI,CAAC;oBAEpB,kEAAkE;oBAClE,MAAM,CAAC,GAAG,EAAE,CAAC;oBAEb,wBAAwB;oBACxB,WAAW,EAAE,CAAC;oBAEd,2BAA2B;oBAC3B,OAAO,CAAC,KAAK,EAAE,CAAC;gBACjB,CAAC,CAAC,CAAC;YACJ,CAAC;QACF,CAAC,CAAC,CAAC;QAEH,4BAA4B;QAC5B,OAAO,CAAC,KAAK,CAAC,MAAkC,CAAC,CAAC;QAElD,yCAAyC;QACzC,MAAM,CAAC,GAAG,EAAE,CAAC;QAEb,qBAAqB;QACrB,OAAO,GAAG,EAAE;YACX,IAAI,OAAO,OAAO,KAAK,UAAU;gBAAE,OAAO,EAAE,CAAC;YAC7C,2CAA2C;YAC3C,OAAO,CAAC,OAAO,CAAC,MAAkC,CAAC,CAAC;QACrD,CAAC,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,sEAAsE;AAC/E,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmachines/play-react",
3
- "version": "1.0.0-beta.8",
3
+ "version": "1.0.0-beta.9",
4
4
  "description": "React renderer for XMachines Play architecture with signal-driven rendering",
5
5
  "keywords": [
6
6
  "actor",
@@ -44,25 +44,23 @@
44
44
  "prepublishOnly": "npm run build"
45
45
  },
46
46
  "dependencies": {
47
- "@xmachines/play-actor": "1.0.0-beta.8",
48
- "@xmachines/play-catalog": "1.0.0-beta.8",
49
- "@xmachines/play-signals": "1.0.0-beta.8"
47
+ "@xmachines/play-actor": "1.0.0-beta.9",
48
+ "@xmachines/play-catalog": "1.0.0-beta.9",
49
+ "@xmachines/play-signals": "1.0.0-beta.9"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@testing-library/jest-dom": "^6.9.1",
53
53
  "@testing-library/react": "^16.3.2",
54
- "@types/jsdom": "^28.0.1",
55
54
  "@types/node": "^25.5.0",
56
55
  "@types/react": "^19.2.14",
57
56
  "@types/react-dom": "^19.2.3",
58
- "@xmachines/shared": "1.0.0-beta.8",
59
- "jsdom": "^29.0.0",
57
+ "@xmachines/shared": "1.0.0-beta.9",
60
58
  "react": "^19.2.4",
61
59
  "react-dom": "^19.2.4",
62
60
  "typescript": "^5.9.3"
63
61
  },
64
62
  "peerDependencies": {
65
- "react": "^18.2.0 || ^19.0.0",
66
- "react-dom": "^18.2.0 || ^19.0.0"
63
+ "react": "^18.0.0 || ^19.0.0",
64
+ "react-dom": "^18.0.0 || ^19.0.0"
67
65
  }
68
66
  }