@xstate/react 7.0.0-alpha.7 → 7.0.0-beta.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 CHANGED
@@ -10,7 +10,7 @@ This package contains utilities for using [XState](https://github.com/statelyai/
10
10
  1. Install `xstate` and `@xstate/react`:
11
11
 
12
12
  ```bash
13
- npm i xstate @xstate/react
13
+ npm i xstate@beta @xstate/react@beta
14
14
  ```
15
15
 
16
16
  2. Import the `useMachine` hook:
@@ -8,5 +8,10 @@ export declare function createActorContext<TLogic extends AnyActorLogic>(actorLo
8
8
  children: React.ReactNode;
9
9
  options?: ActorOptions<TLogic>;
10
10
  logic?: TLogic;
11
+ /**
12
+ * An already-created actor (e.g. from `restoreActor`) to provide instead
13
+ * of creating one from `logic` and `options`. Create it once.
14
+ */
15
+ actor?: Actor<TLogic>;
11
16
  }) => React.ReactElement<any, any>;
12
17
  };
@@ -1,5 +1,12 @@
1
1
  import { Actor, ActorOptions, AnyActorLogic, SnapshotFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys, type RequiredActorOptionsFor } from 'xstate';
2
- /** @public */
2
+ /**
3
+ * Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
4
+ * `restoreActor`) and subscribes to its snapshot. Create a passed actor once,
5
+ * e.g. `useState(() => restoreActor(machine, persisted))[0]`.
6
+ *
7
+ * @public
8
+ */
9
+ export declare function useActor<TLogic extends AnyActorLogic>(actor: Actor<TLogic>): [SnapshotFrom<TLogic>, Actor<TLogic>['send'], Actor<TLogic>];
3
10
  export declare function useActor<TLogic extends AnyActorLogic>(logic: TLogic, ...[options]: ConditionalRequired<[
4
11
  options?: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>
5
12
  ], IsNotNever<RequiredActorOptionsKeys<TLogic>>>): [SnapshotFrom<TLogic>, Actor<TLogic>['send'], Actor<TLogic>];
@@ -1,9 +1,21 @@
1
1
  import { Actor, ActorOptions, AnyActorLogic, Observer, SnapshotFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys, type RequiredActorOptionsFor } from 'xstate';
2
- export declare function useIdleActorRef<TLogic extends AnyActorLogic>(logic: TLogic, ...[options]: ConditionalRequired<[
2
+ /**
3
+ * An already-created actor (e.g. from `restoreActor`). Hooks use it as is
4
+ * instead of creating one from logic.
5
+ */
6
+ export declare function isActor(value: unknown): value is Actor<AnyActorLogic>;
7
+ export declare function useIdleActorRef<TLogic extends AnyActorLogic>(logicOrActor: TLogic | Actor<TLogic>, ...[options]: ConditionalRequired<[
3
8
  options?: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>
4
9
  ], IsNotNever<RequiredActorOptionsKeys<TLogic>>>): [Actor<TLogic>, (actorRef: Actor<TLogic>) => void];
5
10
  export declare function useActorLifecycle<TLogic extends AnyActorLogic>(actorRef: Actor<TLogic>, setActorRef: (actorRef: Actor<TLogic>) => void, createReplacement: () => Actor<TLogic>): void;
6
- /** @public */
11
+ /**
12
+ * Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
13
+ * `restoreActor`), starts it on mount and stops it on unmount. Create a passed
14
+ * actor once, e.g. `useState(() => restoreActor(machine, persisted))[0]`.
15
+ *
16
+ * @public
17
+ */
18
+ export declare function useActorRef<TLogic extends AnyActorLogic>(actor: Actor<TLogic>, observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)): Actor<TLogic>;
7
19
  export declare function useActorRef<TLogic extends AnyActorLogic>(machine: TLogic, ...[options, observerOrListener]: IsNotNever<RequiredActorOptionsKeys<TLogic>> extends true ? [
8
20
  options: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>,
9
21
  observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)
@@ -5,6 +5,7 @@ import { Actor, ActorOptions, AnyStateMachine, StateFrom, type ConditionalRequir
5
5
  * @deprecated Use `useActor(machine, options)`.
6
6
  * @public
7
7
  */
8
+ export declare function useMachine<TMachine extends AnyStateMachine>(actor: Actor<TMachine>): [StateFrom<TMachine>, Actor<TMachine>['send'], Actor<TMachine>];
8
9
  export declare function useMachine<TMachine extends AnyStateMachine>(machine: TMachine, ...[options]: ConditionalRequired<[
9
10
  options?: ActorOptions<TMachine> & RequiredActorOptionsFor<TMachine>
10
11
  ], IsNotNever<RequiredActorOptionsKeys<TMachine>>>): [StateFrom<TMachine>, Actor<TMachine>['send'], Actor<TMachine>];
@@ -35,15 +35,23 @@ function isStateMachine(logic) {
35
35
  return typeof logic.provide === 'function' && !!logic.config && !!logic.sources;
36
36
  }
37
37
 
38
+ /**
39
+ * An already-created actor (e.g. from `restoreActor`). Hooks use it as is
40
+ * instead of creating one from logic.
41
+ */
42
+ function isActor(value) {
43
+ return value instanceof xstate.Actor;
44
+ }
45
+
38
46
  // The hook updates the sources of its machine on later renders. A per-hook
39
47
  // copy keeps those updates from reaching the caller's machine, which may be
40
48
  // shared by other actors.
41
49
  function ownLogic(logic) {
42
50
  return isStateMachine(logic) ? logic.provide({}) : logic;
43
51
  }
44
- function useIdleActorRef(logic, ...[options]) {
52
+ function useIdleActorRef(logicOrActor, ...[options]) {
45
53
  const [actorRef, setActorRef] = React.useState(() => {
46
- return xstate.createActor(ownLogic(logic), options);
54
+ return isActor(logicOrActor) ? logicOrActor : xstate.createActor(ownLogic(logicOrActor), options);
47
55
  });
48
56
  // An object whose identity changes only when React Fast Refresh re-renders
49
57
  // this component. Fast Refresh ignores dependency lists while it applies an
@@ -57,6 +65,11 @@ function useIdleActorRef(logic, ...[options]) {
57
65
  // `machine.provide()` for the same machine config.
58
66
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
59
67
  useIsomorphicLayoutEffect__default["default"](() => {
68
+ // A ready actor is owned as is; its logic is not updated by later renders.
69
+ if (isActor(logicOrActor)) {
70
+ return;
71
+ }
72
+ const logic = logicOrActor;
60
73
  const currentLogic = actorRef.logic;
61
74
  refreshSignalRef.current !== refreshSignal;
62
75
  refreshSignalRef.current = refreshSignal;
@@ -104,9 +117,19 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
104
117
  }, [actorRef]);
105
118
  }
106
119
 
107
- /** @public */
108
- function useActorRef(machine, ...[options, observerOrListener]) {
109
- const [actorRef, setActorRef] = useIdleActorRef(machine, options);
120
+ /**
121
+ * Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
122
+ * `restoreActor`), starts it on mount and stops it on unmount. Create a passed
123
+ * actor once, e.g. `useState(() => restoreActor(machine, persisted))[0]`.
124
+ *
125
+ * @public
126
+ */
127
+
128
+ function useActorRef(logicOrActor, ...args) {
129
+ const readyActor = isActor(logicOrActor);
130
+ const options = readyActor ? undefined : args[0];
131
+ const observerOrListener = readyActor ? args[0] : args[1];
132
+ const [actorRef, setActorRef] = useIdleActorRef(logicOrActor, options);
110
133
  React.useEffect(() => {
111
134
  if (!observerOrListener) {
112
135
  return;
@@ -156,9 +179,10 @@ function createActorContext(actorLogic, actorOptions) {
156
179
  function Provider({
157
180
  children,
158
181
  logic: providedLogic = actorLogic,
159
- options: providedOptions
182
+ options: providedOptions,
183
+ actor: providedActor
160
184
  }) {
161
- const actor = useActorRef(providedLogic, {
185
+ const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
162
186
  ...actorOptions,
163
187
  ...providedOptions
164
188
  });
@@ -214,9 +238,17 @@ function shallowEqual(objA, objB) {
214
238
  return true;
215
239
  }
216
240
 
217
- /** @public */
218
- function useActor(logic, ...[options]) {
219
- const [actorRef, setActorRef] = useIdleActorRef(logic, options);
241
+ /**
242
+ * Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
243
+ * `restoreActor`) and subscribes to its snapshot. Create a passed actor once,
244
+ * e.g. `useState(() => restoreActor(machine, persisted))[0]`.
245
+ *
246
+ * @public
247
+ */
248
+
249
+ function useActor(logic, options) {
250
+ const readyActor = isActor(logic);
251
+ const [actorRef, setActorRef] = useIdleActorRef(logic, ...[options]);
220
252
  const getSnapshot = React.useCallback(() => {
221
253
  return actorRef.getSnapshot();
222
254
  }, [actorRef]);
@@ -234,7 +266,7 @@ function useActor(logic, ...[options]) {
234
266
  if (snapshotWithStatus?.status === 'error') {
235
267
  throw snapshotWithStatus.error;
236
268
  }
237
- useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, options));
269
+ useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, readyActor ? actorRef.options : options));
238
270
  return [actorSnapshot, actorRef.send, actorRef];
239
271
  }
240
272
 
@@ -244,7 +276,8 @@ function useActor(logic, ...[options]) {
244
276
  * @deprecated Use `useActor(machine, options)`.
245
277
  * @public
246
278
  */
247
- function useMachine(machine, ...[options]) {
279
+
280
+ function useMachine(machine, options) {
248
281
  return useActor(machine, options);
249
282
  }
250
283
 
@@ -35,15 +35,23 @@ function isStateMachine(logic) {
35
35
  return typeof logic.provide === 'function' && !!logic.config && !!logic.sources;
36
36
  }
37
37
 
38
+ /**
39
+ * An already-created actor (e.g. from `restoreActor`). Hooks use it as is
40
+ * instead of creating one from logic.
41
+ */
42
+ function isActor(value) {
43
+ return value instanceof xstate.Actor;
44
+ }
45
+
38
46
  // The hook updates the sources of its machine on later renders. A per-hook
39
47
  // copy keeps those updates from reaching the caller's machine, which may be
40
48
  // shared by other actors.
41
49
  function ownLogic(logic) {
42
50
  return isStateMachine(logic) ? logic.provide({}) : logic;
43
51
  }
44
- function useIdleActorRef(logic, ...[options]) {
52
+ function useIdleActorRef(logicOrActor, ...[options]) {
45
53
  const [actorRef, setActorRef] = React.useState(() => {
46
- return xstate.createActor(ownLogic(logic), options);
54
+ return isActor(logicOrActor) ? logicOrActor : xstate.createActor(ownLogic(logicOrActor), options);
47
55
  });
48
56
  // An object whose identity changes only when React Fast Refresh re-renders
49
57
  // this component. Fast Refresh ignores dependency lists while it applies an
@@ -57,6 +65,11 @@ function useIdleActorRef(logic, ...[options]) {
57
65
  // `machine.provide()` for the same machine config.
58
66
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
59
67
  useIsomorphicLayoutEffect__default["default"](() => {
68
+ // A ready actor is owned as is; its logic is not updated by later renders.
69
+ if (isActor(logicOrActor)) {
70
+ return;
71
+ }
72
+ const logic = logicOrActor;
60
73
  const currentLogic = actorRef.logic;
61
74
  const refreshed = refreshSignalRef.current !== refreshSignal;
62
75
  refreshSignalRef.current = refreshSignal;
@@ -114,9 +127,19 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
114
127
  }, [actorRef]);
115
128
  }
116
129
 
117
- /** @public */
118
- function useActorRef(machine, ...[options, observerOrListener]) {
119
- const [actorRef, setActorRef] = useIdleActorRef(machine, options);
130
+ /**
131
+ * Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
132
+ * `restoreActor`), starts it on mount and stops it on unmount. Create a passed
133
+ * actor once, e.g. `useState(() => restoreActor(machine, persisted))[0]`.
134
+ *
135
+ * @public
136
+ */
137
+
138
+ function useActorRef(logicOrActor, ...args) {
139
+ const readyActor = isActor(logicOrActor);
140
+ const options = readyActor ? undefined : args[0];
141
+ const observerOrListener = readyActor ? args[0] : args[1];
142
+ const [actorRef, setActorRef] = useIdleActorRef(logicOrActor, options);
120
143
  React.useEffect(() => {
121
144
  if (!observerOrListener) {
122
145
  return;
@@ -166,9 +189,10 @@ function createActorContext(actorLogic, actorOptions) {
166
189
  function Provider({
167
190
  children,
168
191
  logic: providedLogic = actorLogic,
169
- options: providedOptions
192
+ options: providedOptions,
193
+ actor: providedActor
170
194
  }) {
171
- const actor = useActorRef(providedLogic, {
195
+ const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
172
196
  ...actorOptions,
173
197
  ...providedOptions
174
198
  });
@@ -224,12 +248,20 @@ function shallowEqual(objA, objB) {
224
248
  return true;
225
249
  }
226
250
 
227
- /** @public */
228
- function useActor(logic, ...[options]) {
229
- if (!!logic && 'send' in logic && typeof logic.send === 'function') {
230
- throw new Error(`useActor() expects actor logic (e.g. a machine), but received an ActorRef. Use the useSelector(actorRef, ...) hook instead to read the ActorRef's snapshot.`);
251
+ /**
252
+ * Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
253
+ * `restoreActor`) and subscribes to its snapshot. Create a passed actor once,
254
+ * e.g. `useState(() => restoreActor(machine, persisted))[0]`.
255
+ *
256
+ * @public
257
+ */
258
+
259
+ function useActor(logic, options) {
260
+ const readyActor = isActor(logic);
261
+ if (!readyActor && !!logic && 'send' in logic && typeof logic.send === 'function') {
262
+ throw new Error(`useActor() expects actor logic (e.g. a machine), but received an ActorRef. Pass an Actor created with createActor()/restoreActor(), or use the useSelector(actorRef, ...) hook to read an ActorRef's snapshot.`);
231
263
  }
232
- const [actorRef, setActorRef] = useIdleActorRef(logic, options);
264
+ const [actorRef, setActorRef] = useIdleActorRef(logic, ...[options]);
233
265
  const getSnapshot = React.useCallback(() => {
234
266
  return actorRef.getSnapshot();
235
267
  }, [actorRef]);
@@ -247,7 +279,7 @@ function useActor(logic, ...[options]) {
247
279
  if (snapshotWithStatus?.status === 'error') {
248
280
  throw snapshotWithStatus.error;
249
281
  }
250
- useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, options));
282
+ useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, readyActor ? actorRef.options : options));
251
283
  return [actorSnapshot, actorRef.send, actorRef];
252
284
  }
253
285
 
@@ -257,7 +289,8 @@ function useActor(logic, ...[options]) {
257
289
  * @deprecated Use `useActor(machine, options)`.
258
290
  * @public
259
291
  */
260
- function useMachine(machine, ...[options]) {
292
+
293
+ function useMachine(machine, options) {
261
294
  return useActor(machine, options);
262
295
  }
263
296
 
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { useEffect, useState, useMemo, useRef, useCallback } from 'react';
3
3
  import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
4
- import { _toObserver, createActor, _hotSwapActorLogic } from 'xstate';
4
+ import { _toObserver, createActor, _hotSwapActorLogic, Actor } from 'xstate';
5
5
  import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
6
6
  import { useSyncExternalStore } from 'use-sync-external-store/shim';
7
7
 
@@ -9,15 +9,23 @@ function isStateMachine(logic) {
9
9
  return typeof logic.provide === 'function' && !!logic.config && !!logic.sources;
10
10
  }
11
11
 
12
+ /**
13
+ * An already-created actor (e.g. from `restoreActor`). Hooks use it as is
14
+ * instead of creating one from logic.
15
+ */
16
+ function isActor(value) {
17
+ return value instanceof Actor;
18
+ }
19
+
12
20
  // The hook updates the sources of its machine on later renders. A per-hook
13
21
  // copy keeps those updates from reaching the caller's machine, which may be
14
22
  // shared by other actors.
15
23
  function ownLogic(logic) {
16
24
  return isStateMachine(logic) ? logic.provide({}) : logic;
17
25
  }
18
- function useIdleActorRef(logic, ...[options]) {
26
+ function useIdleActorRef(logicOrActor, ...[options]) {
19
27
  const [actorRef, setActorRef] = useState(() => {
20
- return createActor(ownLogic(logic), options);
28
+ return isActor(logicOrActor) ? logicOrActor : createActor(ownLogic(logicOrActor), options);
21
29
  });
22
30
  // An object whose identity changes only when React Fast Refresh re-renders
23
31
  // this component. Fast Refresh ignores dependency lists while it applies an
@@ -31,6 +39,11 @@ function useIdleActorRef(logic, ...[options]) {
31
39
  // `machine.provide()` for the same machine config.
32
40
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
33
41
  useIsomorphicLayoutEffect(() => {
42
+ // A ready actor is owned as is; its logic is not updated by later renders.
43
+ if (isActor(logicOrActor)) {
44
+ return;
45
+ }
46
+ const logic = logicOrActor;
34
47
  const currentLogic = actorRef.logic;
35
48
  const refreshed = refreshSignalRef.current !== refreshSignal;
36
49
  refreshSignalRef.current = refreshSignal;
@@ -88,9 +101,19 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
88
101
  }, [actorRef]);
89
102
  }
90
103
 
91
- /** @public */
92
- function useActorRef(machine, ...[options, observerOrListener]) {
93
- const [actorRef, setActorRef] = useIdleActorRef(machine, options);
104
+ /**
105
+ * Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
106
+ * `restoreActor`), starts it on mount and stops it on unmount. Create a passed
107
+ * actor once, e.g. `useState(() => restoreActor(machine, persisted))[0]`.
108
+ *
109
+ * @public
110
+ */
111
+
112
+ function useActorRef(logicOrActor, ...args) {
113
+ const readyActor = isActor(logicOrActor);
114
+ const options = readyActor ? undefined : args[0];
115
+ const observerOrListener = readyActor ? args[0] : args[1];
116
+ const [actorRef, setActorRef] = useIdleActorRef(logicOrActor, options);
94
117
  useEffect(() => {
95
118
  if (!observerOrListener) {
96
119
  return;
@@ -140,9 +163,10 @@ function createActorContext(actorLogic, actorOptions) {
140
163
  function Provider({
141
164
  children,
142
165
  logic: providedLogic = actorLogic,
143
- options: providedOptions
166
+ options: providedOptions,
167
+ actor: providedActor
144
168
  }) {
145
- const actor = useActorRef(providedLogic, {
169
+ const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
146
170
  ...actorOptions,
147
171
  ...providedOptions
148
172
  });
@@ -198,12 +222,20 @@ function shallowEqual(objA, objB) {
198
222
  return true;
199
223
  }
200
224
 
201
- /** @public */
202
- function useActor(logic, ...[options]) {
203
- if (!!logic && 'send' in logic && typeof logic.send === 'function') {
204
- throw new Error(`useActor() expects actor logic (e.g. a machine), but received an ActorRef. Use the useSelector(actorRef, ...) hook instead to read the ActorRef's snapshot.`);
225
+ /**
226
+ * Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
227
+ * `restoreActor`) and subscribes to its snapshot. Create a passed actor once,
228
+ * e.g. `useState(() => restoreActor(machine, persisted))[0]`.
229
+ *
230
+ * @public
231
+ */
232
+
233
+ function useActor(logic, options) {
234
+ const readyActor = isActor(logic);
235
+ if (!readyActor && !!logic && 'send' in logic && typeof logic.send === 'function') {
236
+ throw new Error(`useActor() expects actor logic (e.g. a machine), but received an ActorRef. Pass an Actor created with createActor()/restoreActor(), or use the useSelector(actorRef, ...) hook to read an ActorRef's snapshot.`);
205
237
  }
206
- const [actorRef, setActorRef] = useIdleActorRef(logic, options);
238
+ const [actorRef, setActorRef] = useIdleActorRef(logic, ...[options]);
207
239
  const getSnapshot = useCallback(() => {
208
240
  return actorRef.getSnapshot();
209
241
  }, [actorRef]);
@@ -221,7 +253,7 @@ function useActor(logic, ...[options]) {
221
253
  if (snapshotWithStatus?.status === 'error') {
222
254
  throw snapshotWithStatus.error;
223
255
  }
224
- useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
256
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, readyActor ? actorRef.options : options));
225
257
  return [actorSnapshot, actorRef.send, actorRef];
226
258
  }
227
259
 
@@ -231,7 +263,8 @@ function useActor(logic, ...[options]) {
231
263
  * @deprecated Use `useActor(machine, options)`.
232
264
  * @public
233
265
  */
234
- function useMachine(machine, ...[options]) {
266
+
267
+ function useMachine(machine, options) {
235
268
  return useActor(machine, options);
236
269
  }
237
270
 
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { useEffect, useState, useMemo, useRef, useCallback } from 'react';
3
3
  import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
4
- import { _toObserver, createActor } from 'xstate';
4
+ import { _toObserver, createActor, Actor } from 'xstate';
5
5
  import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
6
6
  import { useSyncExternalStore } from 'use-sync-external-store/shim';
7
7
 
@@ -9,15 +9,23 @@ function isStateMachine(logic) {
9
9
  return typeof logic.provide === 'function' && !!logic.config && !!logic.sources;
10
10
  }
11
11
 
12
+ /**
13
+ * An already-created actor (e.g. from `restoreActor`). Hooks use it as is
14
+ * instead of creating one from logic.
15
+ */
16
+ function isActor(value) {
17
+ return value instanceof Actor;
18
+ }
19
+
12
20
  // The hook updates the sources of its machine on later renders. A per-hook
13
21
  // copy keeps those updates from reaching the caller's machine, which may be
14
22
  // shared by other actors.
15
23
  function ownLogic(logic) {
16
24
  return isStateMachine(logic) ? logic.provide({}) : logic;
17
25
  }
18
- function useIdleActorRef(logic, ...[options]) {
26
+ function useIdleActorRef(logicOrActor, ...[options]) {
19
27
  const [actorRef, setActorRef] = useState(() => {
20
- return createActor(ownLogic(logic), options);
28
+ return isActor(logicOrActor) ? logicOrActor : createActor(ownLogic(logicOrActor), options);
21
29
  });
22
30
  // An object whose identity changes only when React Fast Refresh re-renders
23
31
  // this component. Fast Refresh ignores dependency lists while it applies an
@@ -31,6 +39,11 @@ function useIdleActorRef(logic, ...[options]) {
31
39
  // `machine.provide()` for the same machine config.
32
40
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
33
41
  useIsomorphicLayoutEffect(() => {
42
+ // A ready actor is owned as is; its logic is not updated by later renders.
43
+ if (isActor(logicOrActor)) {
44
+ return;
45
+ }
46
+ const logic = logicOrActor;
34
47
  const currentLogic = actorRef.logic;
35
48
  refreshSignalRef.current !== refreshSignal;
36
49
  refreshSignalRef.current = refreshSignal;
@@ -78,9 +91,19 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
78
91
  }, [actorRef]);
79
92
  }
80
93
 
81
- /** @public */
82
- function useActorRef(machine, ...[options, observerOrListener]) {
83
- const [actorRef, setActorRef] = useIdleActorRef(machine, options);
94
+ /**
95
+ * Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
96
+ * `restoreActor`), starts it on mount and stops it on unmount. Create a passed
97
+ * actor once, e.g. `useState(() => restoreActor(machine, persisted))[0]`.
98
+ *
99
+ * @public
100
+ */
101
+
102
+ function useActorRef(logicOrActor, ...args) {
103
+ const readyActor = isActor(logicOrActor);
104
+ const options = readyActor ? undefined : args[0];
105
+ const observerOrListener = readyActor ? args[0] : args[1];
106
+ const [actorRef, setActorRef] = useIdleActorRef(logicOrActor, options);
84
107
  useEffect(() => {
85
108
  if (!observerOrListener) {
86
109
  return;
@@ -130,9 +153,10 @@ function createActorContext(actorLogic, actorOptions) {
130
153
  function Provider({
131
154
  children,
132
155
  logic: providedLogic = actorLogic,
133
- options: providedOptions
156
+ options: providedOptions,
157
+ actor: providedActor
134
158
  }) {
135
- const actor = useActorRef(providedLogic, {
159
+ const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
136
160
  ...actorOptions,
137
161
  ...providedOptions
138
162
  });
@@ -188,9 +212,17 @@ function shallowEqual(objA, objB) {
188
212
  return true;
189
213
  }
190
214
 
191
- /** @public */
192
- function useActor(logic, ...[options]) {
193
- const [actorRef, setActorRef] = useIdleActorRef(logic, options);
215
+ /**
216
+ * Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
217
+ * `restoreActor`) and subscribes to its snapshot. Create a passed actor once,
218
+ * e.g. `useState(() => restoreActor(machine, persisted))[0]`.
219
+ *
220
+ * @public
221
+ */
222
+
223
+ function useActor(logic, options) {
224
+ const readyActor = isActor(logic);
225
+ const [actorRef, setActorRef] = useIdleActorRef(logic, ...[options]);
194
226
  const getSnapshot = useCallback(() => {
195
227
  return actorRef.getSnapshot();
196
228
  }, [actorRef]);
@@ -208,7 +240,7 @@ function useActor(logic, ...[options]) {
208
240
  if (snapshotWithStatus?.status === 'error') {
209
241
  throw snapshotWithStatus.error;
210
242
  }
211
- useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
243
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, readyActor ? actorRef.options : options));
212
244
  return [actorSnapshot, actorRef.send, actorRef];
213
245
  }
214
246
 
@@ -218,7 +250,8 @@ function useActor(logic, ...[options]) {
218
250
  * @deprecated Use `useActor(machine, options)`.
219
251
  * @public
220
252
  */
221
- function useMachine(machine, ...[options]) {
253
+
254
+ function useMachine(machine, options) {
222
255
  return useActor(machine, options);
223
256
  }
224
257
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xstate/react",
3
- "version": "7.0.0-alpha.7",
3
+ "version": "7.0.0-beta.0",
4
4
  "description": "XState tools for React",
5
5
  "keywords": [
6
6
  "state",
@@ -54,7 +54,7 @@
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
57
- "xstate": "^6.0.0-alpha.73"
57
+ "xstate": "^6.0.0-beta.0"
58
58
  },
59
59
  "dependencies": {
60
60
  "use-isomorphic-layout-effect": "^1.1.2",
@@ -69,7 +69,7 @@
69
69
  "react-dom": "^19.0.0",
70
70
  "rxjs": "^7.8.1",
71
71
  "zod": "^3.25.51",
72
- "xstate": "^6.0.0-alpha.73"
72
+ "xstate": "^6.0.0-beta.0"
73
73
  },
74
74
  "scripts": {}
75
75
  }