@xstate/react 7.0.0-alpha.3 → 7.0.0-alpha.4

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
@@ -49,4 +49,4 @@ export const Toggler = () => {
49
49
 
50
50
  <!-- observer lifecycle from src/useActorRef.ts -->
51
51
 
52
- Pass a snapshot listener or observer as the third argument to `useActorRef(logic, options, observer)`. It subscribes before the actor starts. When changing the machine configuration replaces the actor, the observer follows the replacement, even when the callback identity stays the same. Unmounting unsubscribes the observer and stops the actor.
52
+ Pass a snapshot listener or observer as the third argument to `useActorRef(logic, options, observer)`. It subscribes before the actor starts. When the hook replaces the actor (after an external stop, or a Fast Refresh that cannot keep it), the observer follows the replacement, even when the callback identity stays the same. Unmounting unsubscribes the observer and stops the actor.
@@ -1,6 +1,4 @@
1
- import { Actor, ActorOptions, AnyActorLogic, SnapshotFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys } from 'xstate';
1
+ import { Actor, ActorOptions, AnyActorLogic, SnapshotFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys, type RequiredActorOptionsFor } from 'xstate';
2
2
  export declare function useActor<TLogic extends AnyActorLogic>(logic: TLogic, ...[options]: ConditionalRequired<[
3
- options?: ActorOptions<TLogic> & {
4
- [K in RequiredActorOptionsKeys<TLogic>]: unknown;
5
- }
3
+ options?: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>
6
4
  ], IsNotNever<RequiredActorOptionsKeys<TLogic>>>): [SnapshotFrom<TLogic>, Actor<TLogic>['send'], Actor<TLogic>];
@@ -1,14 +1,10 @@
1
- import { Actor, ActorOptions, AnyActorLogic, Observer, SnapshotFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys } from 'xstate';
1
+ import { Actor, ActorOptions, AnyActorLogic, Observer, SnapshotFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys, type RequiredActorOptionsFor } from 'xstate';
2
2
  export declare function useIdleActorRef<TLogic extends AnyActorLogic>(logic: TLogic, ...[options]: ConditionalRequired<[
3
- options?: ActorOptions<TLogic> & {
4
- [K in RequiredActorOptionsKeys<TLogic>]: unknown;
5
- }
3
+ options?: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>
6
4
  ], IsNotNever<RequiredActorOptionsKeys<TLogic>>>): [Actor<TLogic>, (actorRef: Actor<TLogic>) => void];
7
5
  export declare function useActorLifecycle<TLogic extends AnyActorLogic>(actorRef: Actor<TLogic>, setActorRef: (actorRef: Actor<TLogic>) => void, createReplacement: () => Actor<TLogic>): void;
8
6
  export declare function useActorRef<TLogic extends AnyActorLogic>(machine: TLogic, ...[options, observerOrListener]: IsNotNever<RequiredActorOptionsKeys<TLogic>> extends true ? [
9
- options: ActorOptions<TLogic> & {
10
- [K in RequiredActorOptionsKeys<TLogic>]: unknown;
11
- },
7
+ options: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>,
12
8
  observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)
13
9
  ] : [
14
10
  options?: ActorOptions<TLogic>,
@@ -1,7 +1,5 @@
1
- import { Actor, ActorOptions, AnyStateMachine, StateFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys } from 'xstate';
1
+ import { Actor, ActorOptions, AnyStateMachine, StateFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys, type RequiredActorOptionsFor } from 'xstate';
2
2
  /** @alias useActor */
3
3
  export declare function useMachine<TMachine extends AnyStateMachine>(machine: TMachine, ...[options]: ConditionalRequired<[
4
- options?: ActorOptions<TMachine> & {
5
- [K in RequiredActorOptionsKeys<TMachine>]: unknown;
6
- }
4
+ options?: ActorOptions<TMachine> & RequiredActorOptionsFor<TMachine>
7
5
  ], IsNotNever<RequiredActorOptionsKeys<TMachine>>>): [StateFrom<TMachine>, Actor<TMachine>['send'], Actor<TMachine>];
@@ -32,23 +32,28 @@ var React__namespace = /*#__PURE__*/_interopNamespace(React);
32
32
  var useIsomorphicLayoutEffect__default = /*#__PURE__*/_interopDefault(useIsomorphicLayoutEffect);
33
33
 
34
34
  function useIdleActorRef(logic, ...[options]) {
35
- let [actorRef, setActorRef] = React.useState(() => {
35
+ const [actorRef, setActorRef] = React.useState(() => {
36
36
  return xstate.createActor(logic, options);
37
37
  });
38
- if (logic.config !== actorRef.logic.config) {
39
- const newActorRef = xstate.createActor(logic, {
40
- ...options,
41
- snapshot: actorRef.getPersistedSnapshot({
42
- __unsafeAllowInlineActors: true
43
- })
44
- });
45
- setActorRef(newActorRef);
46
- actorRef = newActorRef;
47
- }
38
+ // An object whose identity changes only when React Fast Refresh re-renders
39
+ // this component. Fast Refresh ignores dependency lists while it applies an
40
+ // update, so a `useMemo` with no dependencies recomputes during a refresh
41
+ // and at no other time.
42
+ const refreshSignal = React.useMemo(() => ({}), []);
43
+ const refreshSignalRef = React.useRef(refreshSignal);
48
44
 
45
+ // The logic passed on the first render is used for the hook's lifetime.
46
+ // Later renders only contribute implementations provided with
47
+ // `machine.provide()` for the same machine config.
49
48
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
50
49
  useIsomorphicLayoutEffect__default["default"](() => {
51
- actorRef.logic.sources = logic.sources;
50
+ const currentLogic = actorRef.logic;
51
+ refreshSignalRef.current !== refreshSignal;
52
+ refreshSignalRef.current = refreshSignal;
53
+ if (logic.config === currentLogic.config) {
54
+ currentLogic.sources = logic.sources;
55
+ return;
56
+ }
52
57
  });
53
58
  return [actorRef, setActorRef];
54
59
  }
@@ -97,7 +102,7 @@ function useActorRef(machine, ...[options, observerOrListener]) {
97
102
  sub.unsubscribe();
98
103
  };
99
104
  }, [actorRef, observerOrListener]);
100
- useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(machine, actorRef.options));
105
+ useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, actorRef.options));
101
106
  return actorRef;
102
107
  }
103
108
 
@@ -213,7 +218,7 @@ function useActor(logic, ...[options]) {
213
218
  if (snapshotWithStatus?.status === 'error') {
214
219
  throw snapshotWithStatus.error;
215
220
  }
216
- useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(logic, options));
221
+ useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, options));
217
222
  return [actorSnapshot, actorRef.send, actorRef];
218
223
  }
219
224
 
@@ -32,23 +32,37 @@ var React__namespace = /*#__PURE__*/_interopNamespace(React);
32
32
  var useIsomorphicLayoutEffect__default = /*#__PURE__*/_interopDefault(useIsomorphicLayoutEffect);
33
33
 
34
34
  function useIdleActorRef(logic, ...[options]) {
35
- let [actorRef, setActorRef] = React.useState(() => {
35
+ const [actorRef, setActorRef] = React.useState(() => {
36
36
  return xstate.createActor(logic, options);
37
37
  });
38
- if (logic.config !== actorRef.logic.config) {
39
- const newActorRef = xstate.createActor(logic, {
40
- ...options,
41
- snapshot: actorRef.getPersistedSnapshot({
42
- __unsafeAllowInlineActors: true
43
- })
44
- });
45
- setActorRef(newActorRef);
46
- actorRef = newActorRef;
47
- }
38
+ // An object whose identity changes only when React Fast Refresh re-renders
39
+ // this component. Fast Refresh ignores dependency lists while it applies an
40
+ // update, so a `useMemo` with no dependencies recomputes during a refresh
41
+ // and at no other time.
42
+ const refreshSignal = React.useMemo(() => ({}), []);
43
+ const refreshSignalRef = React.useRef(refreshSignal);
48
44
 
45
+ // The logic passed on the first render is used for the hook's lifetime.
46
+ // Later renders only contribute implementations provided with
47
+ // `machine.provide()` for the same machine config.
49
48
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
50
49
  useIsomorphicLayoutEffect__default["default"](() => {
51
- actorRef.logic.sources = logic.sources;
50
+ const currentLogic = actorRef.logic;
51
+ const refreshed = refreshSignalRef.current !== refreshSignal;
52
+ refreshSignalRef.current = refreshSignal;
53
+ if (logic.config === currentLogic.config) {
54
+ currentLogic.sources = logic.sources;
55
+ return;
56
+ }
57
+
58
+ // Development hot reloading: Fast Refresh re-evaluated the module that
59
+ // defines the machine. Keep the running actor and carry its live snapshot
60
+ // over to the new machine; start a fresh actor if it cannot be carried.
61
+ if (refreshed) {
62
+ if (!xstate._hotSwapActorLogic(actorRef, logic)) {
63
+ setActorRef(xstate.createActor(logic, options));
64
+ }
65
+ }
52
66
  });
53
67
  return [actorRef, setActorRef];
54
68
  }
@@ -97,7 +111,7 @@ function useActorRef(machine, ...[options, observerOrListener]) {
97
111
  sub.unsubscribe();
98
112
  };
99
113
  }, [actorRef, observerOrListener]);
100
- useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(machine, actorRef.options));
114
+ useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, actorRef.options));
101
115
  return actorRef;
102
116
  }
103
117
 
@@ -216,7 +230,7 @@ function useActor(logic, ...[options]) {
216
230
  if (snapshotWithStatus?.status === 'error') {
217
231
  throw snapshotWithStatus.error;
218
232
  }
219
- useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(logic, options));
233
+ useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, options));
220
234
  return [actorSnapshot, actorRef.send, actorRef];
221
235
  }
222
236
 
@@ -1,28 +1,42 @@
1
1
  import * as React from 'react';
2
- import { useEffect, useState, useRef, useCallback } from 'react';
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, _hotSwapActorLogic } 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
 
8
8
  function useIdleActorRef(logic, ...[options]) {
9
- let [actorRef, setActorRef] = useState(() => {
9
+ const [actorRef, setActorRef] = useState(() => {
10
10
  return createActor(logic, options);
11
11
  });
12
- if (logic.config !== actorRef.logic.config) {
13
- const newActorRef = createActor(logic, {
14
- ...options,
15
- snapshot: actorRef.getPersistedSnapshot({
16
- __unsafeAllowInlineActors: true
17
- })
18
- });
19
- setActorRef(newActorRef);
20
- actorRef = newActorRef;
21
- }
12
+ // An object whose identity changes only when React Fast Refresh re-renders
13
+ // this component. Fast Refresh ignores dependency lists while it applies an
14
+ // update, so a `useMemo` with no dependencies recomputes during a refresh
15
+ // and at no other time.
16
+ const refreshSignal = useMemo(() => ({}), []);
17
+ const refreshSignalRef = useRef(refreshSignal);
22
18
 
19
+ // The logic passed on the first render is used for the hook's lifetime.
20
+ // Later renders only contribute implementations provided with
21
+ // `machine.provide()` for the same machine config.
23
22
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
24
23
  useIsomorphicLayoutEffect(() => {
25
- actorRef.logic.sources = logic.sources;
24
+ const currentLogic = actorRef.logic;
25
+ const refreshed = refreshSignalRef.current !== refreshSignal;
26
+ refreshSignalRef.current = refreshSignal;
27
+ if (logic.config === currentLogic.config) {
28
+ currentLogic.sources = logic.sources;
29
+ return;
30
+ }
31
+
32
+ // Development hot reloading: Fast Refresh re-evaluated the module that
33
+ // defines the machine. Keep the running actor and carry its live snapshot
34
+ // over to the new machine; start a fresh actor if it cannot be carried.
35
+ if (refreshed) {
36
+ if (!_hotSwapActorLogic(actorRef, logic)) {
37
+ setActorRef(createActor(logic, options));
38
+ }
39
+ }
26
40
  });
27
41
  return [actorRef, setActorRef];
28
42
  }
@@ -71,7 +85,7 @@ function useActorRef(machine, ...[options, observerOrListener]) {
71
85
  sub.unsubscribe();
72
86
  };
73
87
  }, [actorRef, observerOrListener]);
74
- useActorLifecycle(actorRef, setActorRef, () => createActor(machine, actorRef.options));
88
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, actorRef.options));
75
89
  return actorRef;
76
90
  }
77
91
 
@@ -190,7 +204,7 @@ function useActor(logic, ...[options]) {
190
204
  if (snapshotWithStatus?.status === 'error') {
191
205
  throw snapshotWithStatus.error;
192
206
  }
193
- useActorLifecycle(actorRef, setActorRef, () => createActor(logic, options));
207
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
194
208
  return [actorSnapshot, actorRef.send, actorRef];
195
209
  }
196
210
 
@@ -1,28 +1,33 @@
1
1
  import * as React from 'react';
2
- import { useEffect, useState, useRef, useCallback } from 'react';
2
+ import { useEffect, useState, useMemo, useRef, useCallback } from 'react';
3
3
  import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
4
4
  import { toObserver, createActor } 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
 
8
8
  function useIdleActorRef(logic, ...[options]) {
9
- let [actorRef, setActorRef] = useState(() => {
9
+ const [actorRef, setActorRef] = useState(() => {
10
10
  return createActor(logic, options);
11
11
  });
12
- if (logic.config !== actorRef.logic.config) {
13
- const newActorRef = createActor(logic, {
14
- ...options,
15
- snapshot: actorRef.getPersistedSnapshot({
16
- __unsafeAllowInlineActors: true
17
- })
18
- });
19
- setActorRef(newActorRef);
20
- actorRef = newActorRef;
21
- }
12
+ // An object whose identity changes only when React Fast Refresh re-renders
13
+ // this component. Fast Refresh ignores dependency lists while it applies an
14
+ // update, so a `useMemo` with no dependencies recomputes during a refresh
15
+ // and at no other time.
16
+ const refreshSignal = useMemo(() => ({}), []);
17
+ const refreshSignalRef = useRef(refreshSignal);
22
18
 
19
+ // The logic passed on the first render is used for the hook's lifetime.
20
+ // Later renders only contribute implementations provided with
21
+ // `machine.provide()` for the same machine config.
23
22
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
24
23
  useIsomorphicLayoutEffect(() => {
25
- actorRef.logic.sources = logic.sources;
24
+ const currentLogic = actorRef.logic;
25
+ refreshSignalRef.current !== refreshSignal;
26
+ refreshSignalRef.current = refreshSignal;
27
+ if (logic.config === currentLogic.config) {
28
+ currentLogic.sources = logic.sources;
29
+ return;
30
+ }
26
31
  });
27
32
  return [actorRef, setActorRef];
28
33
  }
@@ -71,7 +76,7 @@ function useActorRef(machine, ...[options, observerOrListener]) {
71
76
  sub.unsubscribe();
72
77
  };
73
78
  }, [actorRef, observerOrListener]);
74
- useActorLifecycle(actorRef, setActorRef, () => createActor(machine, actorRef.options));
79
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, actorRef.options));
75
80
  return actorRef;
76
81
  }
77
82
 
@@ -187,7 +192,7 @@ function useActor(logic, ...[options]) {
187
192
  if (snapshotWithStatus?.status === 'error') {
188
193
  throw snapshotWithStatus.error;
189
194
  }
190
- useActorLifecycle(actorRef, setActorRef, () => createActor(logic, options));
195
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
191
196
  return [actorSnapshot, actorRef.send, actorRef];
192
197
  }
193
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xstate/react",
3
- "version": "7.0.0-alpha.3",
3
+ "version": "7.0.0-alpha.4",
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.59"
57
+ "xstate": "^6.0.0-alpha.61"
58
58
  },
59
59
  "peerDependenciesMeta": {
60
60
  "xstate": {
@@ -74,7 +74,7 @@
74
74
  "react-dom": "^19.0.0",
75
75
  "rxjs": "^7.8.1",
76
76
  "zod": "^3.25.51",
77
- "xstate": "^6.0.0-alpha.59"
77
+ "xstate": "^6.0.0-alpha.61"
78
78
  },
79
79
  "scripts": {}
80
80
  }