@xstate/react 7.0.0-alpha.6 → 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:
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { Actor, ActorOptions, AnyActorLogic, SnapshotFrom } from 'xstate';
3
+ /** @public */
3
4
  export declare function createActorContext<TLogic extends AnyActorLogic>(actorLogic: TLogic, actorOptions?: ActorOptions<TLogic>): {
4
5
  useSelector: <T>(selector: (snapshot: SnapshotFrom<TLogic>) => T, compare?: (a: T, b: T) => boolean) => T;
5
6
  useActorRef: () => Actor<TLogic>;
@@ -7,5 +8,10 @@ export declare function createActorContext<TLogic extends AnyActorLogic>(actorLo
7
8
  children: React.ReactNode;
8
9
  options?: ActorOptions<TLogic>;
9
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>;
10
16
  }) => React.ReactElement<any, any>;
11
17
  };
@@ -1 +1,2 @@
1
+ /** @public */
1
2
  export declare function shallowEqual(objA: any, objB: any): boolean;
@@ -1,4 +1,12 @@
1
1
  import { Actor, ActorOptions, AnyActorLogic, SnapshotFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys, type RequiredActorOptionsFor } from 'xstate';
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>];
2
10
  export declare function useActor<TLogic extends AnyActorLogic>(logic: TLogic, ...[options]: ConditionalRequired<[
3
11
  options?: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>
4
12
  ], IsNotNever<RequiredActorOptionsKeys<TLogic>>>): [SnapshotFrom<TLogic>, Actor<TLogic>['send'], Actor<TLogic>];
@@ -1,8 +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;
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>;
6
19
  export declare function useActorRef<TLogic extends AnyActorLogic>(machine: TLogic, ...[options, observerOrListener]: IsNotNever<RequiredActorOptionsKeys<TLogic>> extends true ? [
7
20
  options: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>,
8
21
  observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)
@@ -1,5 +1,11 @@
1
1
  import { Actor, ActorOptions, AnyStateMachine, StateFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys, type RequiredActorOptionsFor } from 'xstate';
2
- /** @alias useActor */
2
+ /**
3
+ * @alias useActor
4
+ *
5
+ * @deprecated Use `useActor(machine, options)`.
6
+ * @public
7
+ */
8
+ export declare function useMachine<TMachine extends AnyStateMachine>(actor: Actor<TMachine>): [StateFrom<TMachine>, Actor<TMachine>['send'], Actor<TMachine>];
3
9
  export declare function useMachine<TMachine extends AnyStateMachine>(machine: TMachine, ...[options]: ConditionalRequired<[
4
10
  options?: ActorOptions<TMachine> & RequiredActorOptionsFor<TMachine>
5
11
  ], IsNotNever<RequiredActorOptionsKeys<TMachine>>>): [StateFrom<TMachine>, Actor<TMachine>['send'], Actor<TMachine>];
@@ -1,4 +1,5 @@
1
1
  import { AnyActorRef } from 'xstate';
2
+ /** @public */
2
3
  export declare function useSelector<TActor extends Pick<AnyActorRef, 'subscribe' | 'getSnapshot'> | undefined, T>(actor: TActor, selector: (snapshot: TActor extends {
3
4
  getSnapshot(): infer TSnapshot;
4
5
  } ? TSnapshot : undefined) => T, compare?: (a: T, b: T) => boolean): T;
@@ -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;
@@ -103,8 +116,20 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
103
116
  };
104
117
  }, [actorRef]);
105
118
  }
106
- function useActorRef(machine, ...[options, observerOrListener]) {
107
- const [actorRef, setActorRef] = useIdleActorRef(machine, options);
119
+
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);
108
133
  React.useEffect(() => {
109
134
  if (!observerOrListener) {
110
135
  return;
@@ -121,6 +146,8 @@ function useActorRef(machine, ...[options, observerOrListener]) {
121
146
  function defaultCompare(a, b) {
122
147
  return a === b;
123
148
  }
149
+
150
+ /** @public */
124
151
  function useSelector(actor, selector, compare = defaultCompare) {
125
152
  const subscribe = React.useCallback(handleStoreChange => {
126
153
  if (!actor) {
@@ -145,15 +172,17 @@ function useSelector(actor, selector, compare = defaultCompare) {
145
172
  return selectedSnapshot;
146
173
  }
147
174
 
175
+ /** @public */
148
176
  function createActorContext(actorLogic, actorOptions) {
149
177
  const ReactContext = /*#__PURE__*/React__namespace.createContext(null);
150
178
  const OriginalProvider = ReactContext.Provider;
151
179
  function Provider({
152
180
  children,
153
181
  logic: providedLogic = actorLogic,
154
- options: providedOptions
182
+ options: providedOptions,
183
+ actor: providedActor
155
184
  }) {
156
- const actor = useActorRef(providedLogic, {
185
+ const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
157
186
  ...actorOptions,
158
187
  ...providedOptions
159
188
  });
@@ -191,6 +220,8 @@ function is(x, y) {
191
220
  return x !== x && y !== y;
192
221
  }
193
222
  }
223
+
224
+ /** @public */
194
225
  function shallowEqual(objA, objB) {
195
226
  if (is(objA, objB)) return true;
196
227
  if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
@@ -207,8 +238,17 @@ function shallowEqual(objA, objB) {
207
238
  return true;
208
239
  }
209
240
 
210
- function useActor(logic, ...[options]) {
211
- 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]);
212
252
  const getSnapshot = React.useCallback(() => {
213
253
  return actorRef.getSnapshot();
214
254
  }, [actorRef]);
@@ -226,12 +266,18 @@ function useActor(logic, ...[options]) {
226
266
  if (snapshotWithStatus?.status === 'error') {
227
267
  throw snapshotWithStatus.error;
228
268
  }
229
- useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, options));
269
+ useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, readyActor ? actorRef.options : options));
230
270
  return [actorSnapshot, actorRef.send, actorRef];
231
271
  }
232
272
 
233
- /** @alias useActor */
234
- function useMachine(machine, ...[options]) {
273
+ /**
274
+ * @alias useActor
275
+ *
276
+ * @deprecated Use `useActor(machine, options)`.
277
+ * @public
278
+ */
279
+
280
+ function useMachine(machine, options) {
235
281
  return useActor(machine, options);
236
282
  }
237
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;
@@ -113,8 +126,20 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
113
126
  };
114
127
  }, [actorRef]);
115
128
  }
116
- function useActorRef(machine, ...[options, observerOrListener]) {
117
- const [actorRef, setActorRef] = useIdleActorRef(machine, options);
129
+
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);
118
143
  React.useEffect(() => {
119
144
  if (!observerOrListener) {
120
145
  return;
@@ -131,6 +156,8 @@ function useActorRef(machine, ...[options, observerOrListener]) {
131
156
  function defaultCompare(a, b) {
132
157
  return a === b;
133
158
  }
159
+
160
+ /** @public */
134
161
  function useSelector(actor, selector, compare = defaultCompare) {
135
162
  const subscribe = React.useCallback(handleStoreChange => {
136
163
  if (!actor) {
@@ -155,15 +182,17 @@ function useSelector(actor, selector, compare = defaultCompare) {
155
182
  return selectedSnapshot;
156
183
  }
157
184
 
185
+ /** @public */
158
186
  function createActorContext(actorLogic, actorOptions) {
159
187
  const ReactContext = /*#__PURE__*/React__namespace.createContext(null);
160
188
  const OriginalProvider = ReactContext.Provider;
161
189
  function Provider({
162
190
  children,
163
191
  logic: providedLogic = actorLogic,
164
- options: providedOptions
192
+ options: providedOptions,
193
+ actor: providedActor
165
194
  }) {
166
- const actor = useActorRef(providedLogic, {
195
+ const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
167
196
  ...actorOptions,
168
197
  ...providedOptions
169
198
  });
@@ -201,6 +230,8 @@ function is(x, y) {
201
230
  return x !== x && y !== y;
202
231
  }
203
232
  }
233
+
234
+ /** @public */
204
235
  function shallowEqual(objA, objB) {
205
236
  if (is(objA, objB)) return true;
206
237
  if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
@@ -217,11 +248,20 @@ function shallowEqual(objA, objB) {
217
248
  return true;
218
249
  }
219
250
 
220
- function useActor(logic, ...[options]) {
221
- if (!!logic && 'send' in logic && typeof logic.send === 'function') {
222
- 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.`);
223
263
  }
224
- const [actorRef, setActorRef] = useIdleActorRef(logic, options);
264
+ const [actorRef, setActorRef] = useIdleActorRef(logic, ...[options]);
225
265
  const getSnapshot = React.useCallback(() => {
226
266
  return actorRef.getSnapshot();
227
267
  }, [actorRef]);
@@ -239,12 +279,18 @@ function useActor(logic, ...[options]) {
239
279
  if (snapshotWithStatus?.status === 'error') {
240
280
  throw snapshotWithStatus.error;
241
281
  }
242
- useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, options));
282
+ useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, readyActor ? actorRef.options : options));
243
283
  return [actorSnapshot, actorRef.send, actorRef];
244
284
  }
245
285
 
246
- /** @alias useActor */
247
- function useMachine(machine, ...[options]) {
286
+ /**
287
+ * @alias useActor
288
+ *
289
+ * @deprecated Use `useActor(machine, options)`.
290
+ * @public
291
+ */
292
+
293
+ function useMachine(machine, options) {
248
294
  return useActor(machine, options);
249
295
  }
250
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;
@@ -87,8 +100,20 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
87
100
  };
88
101
  }, [actorRef]);
89
102
  }
90
- function useActorRef(machine, ...[options, observerOrListener]) {
91
- const [actorRef, setActorRef] = useIdleActorRef(machine, options);
103
+
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);
92
117
  useEffect(() => {
93
118
  if (!observerOrListener) {
94
119
  return;
@@ -105,6 +130,8 @@ function useActorRef(machine, ...[options, observerOrListener]) {
105
130
  function defaultCompare(a, b) {
106
131
  return a === b;
107
132
  }
133
+
134
+ /** @public */
108
135
  function useSelector(actor, selector, compare = defaultCompare) {
109
136
  const subscribe = useCallback(handleStoreChange => {
110
137
  if (!actor) {
@@ -129,15 +156,17 @@ function useSelector(actor, selector, compare = defaultCompare) {
129
156
  return selectedSnapshot;
130
157
  }
131
158
 
159
+ /** @public */
132
160
  function createActorContext(actorLogic, actorOptions) {
133
161
  const ReactContext = /*#__PURE__*/React.createContext(null);
134
162
  const OriginalProvider = ReactContext.Provider;
135
163
  function Provider({
136
164
  children,
137
165
  logic: providedLogic = actorLogic,
138
- options: providedOptions
166
+ options: providedOptions,
167
+ actor: providedActor
139
168
  }) {
140
- const actor = useActorRef(providedLogic, {
169
+ const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
141
170
  ...actorOptions,
142
171
  ...providedOptions
143
172
  });
@@ -175,6 +204,8 @@ function is(x, y) {
175
204
  return x !== x && y !== y;
176
205
  }
177
206
  }
207
+
208
+ /** @public */
178
209
  function shallowEqual(objA, objB) {
179
210
  if (is(objA, objB)) return true;
180
211
  if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
@@ -191,11 +222,20 @@ function shallowEqual(objA, objB) {
191
222
  return true;
192
223
  }
193
224
 
194
- function useActor(logic, ...[options]) {
195
- if (!!logic && 'send' in logic && typeof logic.send === 'function') {
196
- 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.`);
197
237
  }
198
- const [actorRef, setActorRef] = useIdleActorRef(logic, options);
238
+ const [actorRef, setActorRef] = useIdleActorRef(logic, ...[options]);
199
239
  const getSnapshot = useCallback(() => {
200
240
  return actorRef.getSnapshot();
201
241
  }, [actorRef]);
@@ -213,12 +253,18 @@ function useActor(logic, ...[options]) {
213
253
  if (snapshotWithStatus?.status === 'error') {
214
254
  throw snapshotWithStatus.error;
215
255
  }
216
- useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
256
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, readyActor ? actorRef.options : options));
217
257
  return [actorSnapshot, actorRef.send, actorRef];
218
258
  }
219
259
 
220
- /** @alias useActor */
221
- function useMachine(machine, ...[options]) {
260
+ /**
261
+ * @alias useActor
262
+ *
263
+ * @deprecated Use `useActor(machine, options)`.
264
+ * @public
265
+ */
266
+
267
+ function useMachine(machine, options) {
222
268
  return useActor(machine, options);
223
269
  }
224
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;
@@ -77,8 +90,20 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
77
90
  };
78
91
  }, [actorRef]);
79
92
  }
80
- function useActorRef(machine, ...[options, observerOrListener]) {
81
- const [actorRef, setActorRef] = useIdleActorRef(machine, options);
93
+
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);
82
107
  useEffect(() => {
83
108
  if (!observerOrListener) {
84
109
  return;
@@ -95,6 +120,8 @@ function useActorRef(machine, ...[options, observerOrListener]) {
95
120
  function defaultCompare(a, b) {
96
121
  return a === b;
97
122
  }
123
+
124
+ /** @public */
98
125
  function useSelector(actor, selector, compare = defaultCompare) {
99
126
  const subscribe = useCallback(handleStoreChange => {
100
127
  if (!actor) {
@@ -119,15 +146,17 @@ function useSelector(actor, selector, compare = defaultCompare) {
119
146
  return selectedSnapshot;
120
147
  }
121
148
 
149
+ /** @public */
122
150
  function createActorContext(actorLogic, actorOptions) {
123
151
  const ReactContext = /*#__PURE__*/React.createContext(null);
124
152
  const OriginalProvider = ReactContext.Provider;
125
153
  function Provider({
126
154
  children,
127
155
  logic: providedLogic = actorLogic,
128
- options: providedOptions
156
+ options: providedOptions,
157
+ actor: providedActor
129
158
  }) {
130
- const actor = useActorRef(providedLogic, {
159
+ const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
131
160
  ...actorOptions,
132
161
  ...providedOptions
133
162
  });
@@ -165,6 +194,8 @@ function is(x, y) {
165
194
  return x !== x && y !== y;
166
195
  }
167
196
  }
197
+
198
+ /** @public */
168
199
  function shallowEqual(objA, objB) {
169
200
  if (is(objA, objB)) return true;
170
201
  if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
@@ -181,8 +212,17 @@ function shallowEqual(objA, objB) {
181
212
  return true;
182
213
  }
183
214
 
184
- function useActor(logic, ...[options]) {
185
- 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]);
186
226
  const getSnapshot = useCallback(() => {
187
227
  return actorRef.getSnapshot();
188
228
  }, [actorRef]);
@@ -200,12 +240,18 @@ function useActor(logic, ...[options]) {
200
240
  if (snapshotWithStatus?.status === 'error') {
201
241
  throw snapshotWithStatus.error;
202
242
  }
203
- useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
243
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, readyActor ? actorRef.options : options));
204
244
  return [actorSnapshot, actorRef.send, actorRef];
205
245
  }
206
246
 
207
- /** @alias useActor */
208
- function useMachine(machine, ...[options]) {
247
+ /**
248
+ * @alias useActor
249
+ *
250
+ * @deprecated Use `useActor(machine, options)`.
251
+ * @public
252
+ */
253
+
254
+ function useMachine(machine, options) {
209
255
  return useActor(machine, options);
210
256
  }
211
257
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xstate/react",
3
- "version": "7.0.0-alpha.6",
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.70"
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.70"
72
+ "xstate": "^6.0.0-beta.0"
73
73
  },
74
74
  "scripts": {}
75
75
  }