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

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.
@@ -6,8 +6,6 @@ export declare function createActorContext<TLogic extends AnyActorLogic>(actorLo
6
6
  Provider: (props: {
7
7
  children: React.ReactNode;
8
8
  options?: ActorOptions<TLogic>;
9
- /** @deprecated Use `logic` instead. */
10
- machine?: never;
11
9
  logic?: TLogic;
12
10
  }) => React.ReactElement<any, any>;
13
11
  };
@@ -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
  }
@@ -92,12 +97,12 @@ function useActorRef(machine, ...[options, observerOrListener]) {
92
97
  if (!observerOrListener) {
93
98
  return;
94
99
  }
95
- const sub = actorRef.subscribe(xstate.toObserver(observerOrListener));
100
+ const sub = actorRef.subscribe(xstate._toObserver(observerOrListener));
96
101
  return () => {
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
 
@@ -134,12 +139,8 @@ function createActorContext(actorLogic, actorOptions) {
134
139
  function Provider({
135
140
  children,
136
141
  logic: providedLogic = actorLogic,
137
- machine,
138
142
  options: providedOptions
139
143
  }) {
140
- if (machine) {
141
- throw new Error(`The "machine" prop has been deprecated. Please use "logic" instead.`);
142
- }
143
144
  const actor = useActorRef(providedLogic, {
144
145
  ...actorOptions,
145
146
  ...providedOptions
@@ -213,7 +214,7 @@ function useActor(logic, ...[options]) {
213
214
  if (snapshotWithStatus?.status === 'error') {
214
215
  throw snapshotWithStatus.error;
215
216
  }
216
- useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(logic, options));
217
+ useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, options));
217
218
  return [actorSnapshot, actorRef.send, actorRef];
218
219
  }
219
220
 
@@ -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
  }
@@ -92,12 +106,12 @@ function useActorRef(machine, ...[options, observerOrListener]) {
92
106
  if (!observerOrListener) {
93
107
  return;
94
108
  }
95
- const sub = actorRef.subscribe(xstate.toObserver(observerOrListener));
109
+ const sub = actorRef.subscribe(xstate._toObserver(observerOrListener));
96
110
  return () => {
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
 
@@ -134,12 +148,8 @@ function createActorContext(actorLogic, actorOptions) {
134
148
  function Provider({
135
149
  children,
136
150
  logic: providedLogic = actorLogic,
137
- machine,
138
151
  options: providedOptions
139
152
  }) {
140
- if (machine) {
141
- throw new Error(`The "machine" prop has been deprecated. Please use "logic" instead.`);
142
- }
143
153
  const actor = useActorRef(providedLogic, {
144
154
  ...actorOptions,
145
155
  ...providedOptions
@@ -216,7 +226,7 @@ function useActor(logic, ...[options]) {
216
226
  if (snapshotWithStatus?.status === 'error') {
217
227
  throw snapshotWithStatus.error;
218
228
  }
219
- useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(logic, options));
229
+ useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, options));
220
230
  return [actorSnapshot, actorRef.send, actorRef];
221
231
  }
222
232
 
@@ -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
  }
@@ -66,12 +80,12 @@ function useActorRef(machine, ...[options, observerOrListener]) {
66
80
  if (!observerOrListener) {
67
81
  return;
68
82
  }
69
- const sub = actorRef.subscribe(toObserver(observerOrListener));
83
+ const sub = actorRef.subscribe(_toObserver(observerOrListener));
70
84
  return () => {
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
 
@@ -108,12 +122,8 @@ function createActorContext(actorLogic, actorOptions) {
108
122
  function Provider({
109
123
  children,
110
124
  logic: providedLogic = actorLogic,
111
- machine,
112
125
  options: providedOptions
113
126
  }) {
114
- if (machine) {
115
- throw new Error(`The "machine" prop has been deprecated. Please use "logic" instead.`);
116
- }
117
127
  const actor = useActorRef(providedLogic, {
118
128
  ...actorOptions,
119
129
  ...providedOptions
@@ -190,7 +200,7 @@ function useActor(logic, ...[options]) {
190
200
  if (snapshotWithStatus?.status === 'error') {
191
201
  throw snapshotWithStatus.error;
192
202
  }
193
- useActorLifecycle(actorRef, setActorRef, () => createActor(logic, options));
203
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
194
204
  return [actorSnapshot, actorRef.send, actorRef];
195
205
  }
196
206
 
@@ -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
- import { toObserver, createActor } from 'xstate';
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
  }
@@ -66,12 +71,12 @@ function useActorRef(machine, ...[options, observerOrListener]) {
66
71
  if (!observerOrListener) {
67
72
  return;
68
73
  }
69
- const sub = actorRef.subscribe(toObserver(observerOrListener));
74
+ const sub = actorRef.subscribe(_toObserver(observerOrListener));
70
75
  return () => {
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
 
@@ -108,12 +113,8 @@ function createActorContext(actorLogic, actorOptions) {
108
113
  function Provider({
109
114
  children,
110
115
  logic: providedLogic = actorLogic,
111
- machine,
112
116
  options: providedOptions
113
117
  }) {
114
- if (machine) {
115
- throw new Error(`The "machine" prop has been deprecated. Please use "logic" instead.`);
116
- }
117
118
  const actor = useActorRef(providedLogic, {
118
119
  ...actorOptions,
119
120
  ...providedOptions
@@ -187,7 +188,7 @@ function useActor(logic, ...[options]) {
187
188
  if (snapshotWithStatus?.status === 'error') {
188
189
  throw snapshotWithStatus.error;
189
190
  }
190
- useActorLifecycle(actorRef, setActorRef, () => createActor(logic, options));
191
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
191
192
  return [actorSnapshot, actorRef.send, actorRef];
192
193
  }
193
194
 
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.5",
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.68"
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.68"
78
78
  },
79
79
  "scripts": {}
80
80
  }