@xstate/react 4.0.0-beta.9 → 4.0.1

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.
@@ -1,12 +1,16 @@
1
1
  import * as React from 'react';
2
2
  import { ActorRefFrom, AnyStateMachine, SnapshotFrom, ActorOptions, AreAllImplementationsAssumedToBeProvided, MarkAllImplementationsAsProvided, StateMachine, AnyActorLogic } from 'xstate';
3
- type ToMachinesWithProvidedImplementations<TMachine extends AnyStateMachine> = TMachine extends StateMachine<infer TContext, infer TEvent, infer TActor, infer TAction, infer TGuard, infer TDelay, infer TTag, infer TInput, infer TOutput, infer TResolvedTypesMeta> ? StateMachine<TContext, TEvent, TActor, TAction, TGuard, TDelay, TTag, TInput, TOutput, AreAllImplementationsAssumedToBeProvided<TResolvedTypesMeta> extends false ? MarkAllImplementationsAsProvided<TResolvedTypesMeta> : TResolvedTypesMeta> : never;
3
+ type ToMachinesWithProvidedImplementations<TMachine extends AnyStateMachine> = TMachine extends StateMachine<infer TContext, infer TEvent, infer TChildren, infer TActor, infer TAction, infer TGuard, infer TDelay, infer TStateValue, infer TTag, infer TInput, infer TOutput, infer TResolvedTypesMeta> ? StateMachine<TContext, TEvent, TChildren, TActor, TAction, TGuard, TDelay, TStateValue, TTag, TInput, TOutput, AreAllImplementationsAssumedToBeProvided<TResolvedTypesMeta> extends false ? MarkAllImplementationsAsProvided<TResolvedTypesMeta> : TResolvedTypesMeta> : never;
4
4
  export declare function createActorContext<TLogic extends AnyActorLogic>(actorLogic: TLogic, interpreterOptions?: ActorOptions<TLogic>): {
5
5
  useSelector: <T>(selector: (snapshot: SnapshotFrom<TLogic>) => T, compare?: (a: T, b: T) => boolean) => T;
6
6
  useActorRef: () => ActorRefFrom<TLogic>;
7
7
  Provider: (props: {
8
8
  children: React.ReactNode;
9
9
  options?: ActorOptions<TLogic>;
10
+ /**
11
+ * @deprecated Use `logic` instead.
12
+ */
13
+ machine?: never;
10
14
  } & (TLogic extends AnyStateMachine ? AreAllImplementationsAssumedToBeProvided<TLogic['__TResolvedTypesMeta']> extends true ? {
11
15
  logic?: TLogic;
12
16
  } : {
@@ -1,14 +1,3 @@
1
- import { AnyActorLogic, AnyActor, AnyStateMachine, AreAllImplementationsAssumedToBeProvided, InternalMachineImplementations, ActorRefFrom, ActorOptions, Observer, StateFrom, SnapshotFrom, TODO } from 'xstate';
2
- export declare function useIdleInterpreter(machine: AnyActorLogic, options: Partial<ActorOptions<AnyActorLogic>>): AnyActor;
3
- type RestParams<TLogic extends AnyActorLogic> = TLogic extends AnyStateMachine ? AreAllImplementationsAssumedToBeProvided<TLogic['__TResolvedTypesMeta']> extends false ? [
4
- options: ActorOptions<TLogic> & InternalMachineImplementations<TLogic['__TContext'], TLogic['__TEvent'], TODO, TODO, TODO, TLogic['__TResolvedTypesMeta'], true>,
5
- observerOrListener?: Observer<StateFrom<TLogic>> | ((value: StateFrom<TLogic>) => void)
6
- ] : [
7
- options?: ActorOptions<TLogic> & InternalMachineImplementations<TLogic['__TContext'], TLogic['__TEvent'], TODO, TODO, TODO, TLogic['__TResolvedTypesMeta']>,
8
- observerOrListener?: Observer<StateFrom<TLogic>> | ((value: StateFrom<TLogic>) => void)
9
- ] : [
10
- options?: ActorOptions<TLogic>,
11
- observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)
12
- ];
13
- export declare function useActorRef<TLogic extends AnyActorLogic>(machine: TLogic, ...[options, observerOrListener]: RestParams<TLogic>): ActorRefFrom<TLogic>;
14
- export {};
1
+ import { AnyActorLogic, AnyActor, ActorRefFrom, ActorOptions, Observer, SnapshotFrom } from 'xstate';
2
+ export declare function useIdleActorRef(logic: AnyActorLogic, options: Partial<ActorOptions<AnyActorLogic>>): AnyActor;
3
+ export declare function useActorRef<TLogic extends AnyActorLogic>(machine: TLogic, options?: ActorOptions<TLogic>, observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)): ActorRefFrom<TLogic>;
@@ -1,7 +1,6 @@
1
1
  import { ActorRefFrom, AnyStateMachine, AreAllImplementationsAssumedToBeProvided, ActorOptions, MissingImplementationsError, StateFrom } from 'xstate';
2
2
  /**
3
- *
4
- * @deprecated Use `useActor(...)` instead.
3
+ * @alias useActor
5
4
  */
6
5
  export declare function useMachine<TMachine extends AnyStateMachine>(machine: AreAllImplementationsAssumedToBeProvided<TMachine['__TResolvedTypesMeta']> extends true ? TMachine : MissingImplementationsError<TMachine['__TResolvedTypesMeta']>, options?: ActorOptions<TMachine>): [
7
6
  StateFrom<TMachine>,
@@ -6,7 +6,6 @@ var React = require('react');
6
6
  var shim = require('use-sync-external-store/shim');
7
7
  var xstate = require('xstate');
8
8
  var useIsomorphicLayoutEffect = require('use-isomorphic-layout-effect');
9
- require('xstate/actors');
10
9
  var withSelector = require('use-sync-external-store/shim/with-selector');
11
10
 
12
11
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
@@ -32,29 +31,62 @@ function _interopNamespace(e) {
32
31
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
33
32
  var useIsomorphicLayoutEffect__default = /*#__PURE__*/_interopDefault(useIsomorphicLayoutEffect);
34
33
 
35
- function useConstant(fn) {
36
- const ref = React__namespace.useRef();
37
- if (!ref.current) {
38
- ref.current = {
39
- v: fn()
40
- };
34
+ const forEachActor = (actorRef, callback) => {
35
+ callback(actorRef);
36
+ const children = actorRef.getSnapshot().children;
37
+ if (children) {
38
+ Object.values(children).forEach(child => {
39
+ forEachActor(child, callback);
40
+ });
41
41
  }
42
- return ref.current.v;
42
+ };
43
+ function stopRootWithRehydration(actorRef) {
44
+ // persist snapshot here in a custom way allows us to persist inline actors and to preserve actor references
45
+ // we do it to avoid setState in useEffect when the effect gets "reconnected"
46
+ // this currently only happens in Strict Effects but it simulates the Offscreen aka Activity API
47
+ // it also just allows us to end up with a somewhat more predictable behavior for the users
48
+ const persistedSnapshots = [];
49
+ forEachActor(actorRef, ref => {
50
+ persistedSnapshots.push([ref, ref.getSnapshot()]);
51
+ // muting observers allow us to avoid `useSelector` from being notified about the stopped snapshot
52
+ // React reconnects its subscribers (from the useSyncExternalStore) on its own
53
+ // and userland subscibers should basically always do the same anyway
54
+ // as each subscription should have its own cleanup logic and that should be called each such reconnect
55
+ ref.observers = new Set();
56
+ });
57
+ const systemSnapshot = actorRef.system.getSnapshot?.();
58
+ actorRef.stop();
59
+ actorRef.system._snapshot = systemSnapshot;
60
+ persistedSnapshots.forEach(([ref, snapshot]) => {
61
+ ref._processingStatus = 0;
62
+ ref._snapshot = snapshot;
63
+ });
43
64
  }
44
65
 
45
- function useIdleInterpreter(machine, options) {
46
- const actorRef = useConstant(() => {
47
- return xstate.createActor(machine, options);
66
+ function useIdleActorRef(logic, options) {
67
+ let [[currentConfig, actorRef], setCurrent] = React.useState(() => {
68
+ const actorRef = xstate.createActor(logic, options);
69
+ return [logic.config, actorRef];
48
70
  });
71
+ if (logic.config !== currentConfig) {
72
+ const newActorRef = xstate.createActor(logic, {
73
+ ...options,
74
+ snapshot: actorRef.getPersistedSnapshot({
75
+ __unsafeAllowInlineActors: true
76
+ })
77
+ });
78
+ setCurrent([logic.config, newActorRef]);
79
+ actorRef = newActorRef;
80
+ }
49
81
 
50
82
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
51
83
  useIsomorphicLayoutEffect__default["default"](() => {
52
- actorRef.logic.implementations = machine.implementations;
84
+ actorRef.logic.implementations = logic.implementations;
53
85
  });
54
86
  return actorRef;
55
87
  }
56
- function useActorRef(machine, ...[options = {}, observerOrListener]) {
57
- const actorRef = useIdleInterpreter(machine, options);
88
+ function useActorRef(machine, options = {}, observerOrListener) {
89
+ const actorRef = useIdleActorRef(machine, options);
58
90
  React.useEffect(() => {
59
91
  if (!observerOrListener) {
60
92
  return;
@@ -67,16 +99,14 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
67
99
  React.useEffect(() => {
68
100
  actorRef.start();
69
101
  return () => {
70
- actorRef.stop();
71
- actorRef.status = xstate.ActorStatus.NotStarted;
72
- actorRef._initState();
102
+ stopRootWithRehydration(actorRef);
73
103
  };
74
- }, []);
104
+ }, [actorRef]);
75
105
  return actorRef;
76
106
  }
77
107
 
78
108
  function useActor(logic, options = {}) {
79
- const actorRef = useIdleInterpreter(logic, options);
109
+ const actorRef = useIdleActorRef(logic, options);
80
110
  const getSnapshot = React.useCallback(() => {
81
111
  return actorRef.getSnapshot();
82
112
  }, [actorRef]);
@@ -90,9 +120,7 @@ function useActor(logic, options = {}) {
90
120
  React.useEffect(() => {
91
121
  actorRef.start();
92
122
  return () => {
93
- actorRef.stop();
94
- actorRef.status = xstate.ActorStatus.NotStarted;
95
- actorRef._initState();
123
+ stopRootWithRehydration(actorRef);
96
124
  };
97
125
  }, [actorRef]);
98
126
  return [actorSnapshot, actorRef.send, actorRef];
@@ -177,8 +205,7 @@ function createActorContext(actorLogic, interpreterOptions) {
177
205
  }
178
206
 
179
207
  /**
180
- *
181
- * @deprecated Use `useActor(...)` instead.
208
+ * @alias useActor
182
209
  */
183
210
  function useMachine(machine, options = {}) {
184
211
  return useActor(machine, options);
@@ -6,7 +6,6 @@ var React = require('react');
6
6
  var shim = require('use-sync-external-store/shim');
7
7
  var xstate = require('xstate');
8
8
  var useIsomorphicLayoutEffect = require('use-isomorphic-layout-effect');
9
- var actors = require('xstate/actors');
10
9
  var withSelector = require('use-sync-external-store/shim/with-selector');
11
10
 
12
11
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
@@ -32,35 +31,62 @@ function _interopNamespace(e) {
32
31
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
33
32
  var useIsomorphicLayoutEffect__default = /*#__PURE__*/_interopDefault(useIsomorphicLayoutEffect);
34
33
 
35
- function useConstant(fn) {
36
- const ref = React__namespace.useRef();
37
- if (!ref.current) {
38
- ref.current = {
39
- v: fn()
40
- };
34
+ const forEachActor = (actorRef, callback) => {
35
+ callback(actorRef);
36
+ const children = actorRef.getSnapshot().children;
37
+ if (children) {
38
+ Object.values(children).forEach(child => {
39
+ forEachActor(child, callback);
40
+ });
41
41
  }
42
- return ref.current.v;
42
+ };
43
+ function stopRootWithRehydration(actorRef) {
44
+ // persist snapshot here in a custom way allows us to persist inline actors and to preserve actor references
45
+ // we do it to avoid setState in useEffect when the effect gets "reconnected"
46
+ // this currently only happens in Strict Effects but it simulates the Offscreen aka Activity API
47
+ // it also just allows us to end up with a somewhat more predictable behavior for the users
48
+ const persistedSnapshots = [];
49
+ forEachActor(actorRef, ref => {
50
+ persistedSnapshots.push([ref, ref.getSnapshot()]);
51
+ // muting observers allow us to avoid `useSelector` from being notified about the stopped snapshot
52
+ // React reconnects its subscribers (from the useSyncExternalStore) on its own
53
+ // and userland subscibers should basically always do the same anyway
54
+ // as each subscription should have its own cleanup logic and that should be called each such reconnect
55
+ ref.observers = new Set();
56
+ });
57
+ const systemSnapshot = actorRef.system.getSnapshot?.();
58
+ actorRef.stop();
59
+ actorRef.system._snapshot = systemSnapshot;
60
+ persistedSnapshots.forEach(([ref, snapshot]) => {
61
+ ref._processingStatus = 0;
62
+ ref._snapshot = snapshot;
63
+ });
43
64
  }
44
65
 
45
- function useIdleInterpreter(machine, options) {
46
- {
47
- const [initialMachine] = React.useState(machine);
48
- if (machine.config !== initialMachine.config) {
49
- console.warn(`Actor logic has changed between renders. This is not supported and may lead to invalid snapshots.`);
50
- }
51
- }
52
- const actorRef = useConstant(() => {
53
- return xstate.createActor(machine, options);
66
+ function useIdleActorRef(logic, options) {
67
+ let [[currentConfig, actorRef], setCurrent] = React.useState(() => {
68
+ const actorRef = xstate.createActor(logic, options);
69
+ return [logic.config, actorRef];
54
70
  });
71
+ if (logic.config !== currentConfig) {
72
+ const newActorRef = xstate.createActor(logic, {
73
+ ...options,
74
+ snapshot: actorRef.getPersistedSnapshot({
75
+ __unsafeAllowInlineActors: true
76
+ })
77
+ });
78
+ setCurrent([logic.config, newActorRef]);
79
+ actorRef = newActorRef;
80
+ }
55
81
 
56
82
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
57
83
  useIsomorphicLayoutEffect__default["default"](() => {
58
- actorRef.logic.implementations = machine.implementations;
84
+ actorRef.logic.implementations = logic.implementations;
59
85
  });
60
86
  return actorRef;
61
87
  }
62
- function useActorRef(machine, ...[options = {}, observerOrListener]) {
63
- const actorRef = useIdleInterpreter(machine, options);
88
+ function useActorRef(machine, options = {}, observerOrListener) {
89
+ const actorRef = useIdleActorRef(machine, options);
64
90
  React.useEffect(() => {
65
91
  if (!observerOrListener) {
66
92
  return;
@@ -73,19 +99,17 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
73
99
  React.useEffect(() => {
74
100
  actorRef.start();
75
101
  return () => {
76
- actorRef.stop();
77
- actorRef.status = xstate.ActorStatus.NotStarted;
78
- actorRef._initState();
102
+ stopRootWithRehydration(actorRef);
79
103
  };
80
- }, []);
104
+ }, [actorRef]);
81
105
  return actorRef;
82
106
  }
83
107
 
84
108
  function useActor(logic, options = {}) {
85
- if (actors.isActorRef(logic)) {
109
+ if (!!logic && 'send' in logic && typeof logic.send === 'function') {
86
110
  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.`);
87
111
  }
88
- const actorRef = useIdleInterpreter(logic, options);
112
+ const actorRef = useIdleActorRef(logic, options);
89
113
  const getSnapshot = React.useCallback(() => {
90
114
  return actorRef.getSnapshot();
91
115
  }, [actorRef]);
@@ -99,9 +123,7 @@ function useActor(logic, options = {}) {
99
123
  React.useEffect(() => {
100
124
  actorRef.start();
101
125
  return () => {
102
- actorRef.stop();
103
- actorRef.status = xstate.ActorStatus.NotStarted;
104
- actorRef._initState();
126
+ stopRootWithRehydration(actorRef);
105
127
  };
106
128
  }, [actorRef]);
107
129
  return [actorSnapshot, actorRef.send, actorRef];
@@ -186,8 +208,7 @@ function createActorContext(actorLogic, interpreterOptions) {
186
208
  }
187
209
 
188
210
  /**
189
- *
190
- * @deprecated Use `useActor(...)` instead.
211
+ * @alias useActor
191
212
  */
192
213
  function useMachine(machine, options = {}) {
193
214
  return useActor(machine, options);
@@ -1,40 +1,66 @@
1
1
  import * as React from 'react';
2
2
  import { useEffect, useState, useCallback } from 'react';
3
3
  import { useSyncExternalStore } from 'use-sync-external-store/shim';
4
- import { toObserver, ActorStatus, createActor } from 'xstate';
4
+ import { toObserver, createActor } from 'xstate';
5
5
  import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
6
- import { isActorRef } from 'xstate/actors';
7
6
  import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
8
7
 
9
- function useConstant(fn) {
10
- const ref = React.useRef();
11
- if (!ref.current) {
12
- ref.current = {
13
- v: fn()
14
- };
8
+ const forEachActor = (actorRef, callback) => {
9
+ callback(actorRef);
10
+ const children = actorRef.getSnapshot().children;
11
+ if (children) {
12
+ Object.values(children).forEach(child => {
13
+ forEachActor(child, callback);
14
+ });
15
15
  }
16
- return ref.current.v;
16
+ };
17
+ function stopRootWithRehydration(actorRef) {
18
+ // persist snapshot here in a custom way allows us to persist inline actors and to preserve actor references
19
+ // we do it to avoid setState in useEffect when the effect gets "reconnected"
20
+ // this currently only happens in Strict Effects but it simulates the Offscreen aka Activity API
21
+ // it also just allows us to end up with a somewhat more predictable behavior for the users
22
+ const persistedSnapshots = [];
23
+ forEachActor(actorRef, ref => {
24
+ persistedSnapshots.push([ref, ref.getSnapshot()]);
25
+ // muting observers allow us to avoid `useSelector` from being notified about the stopped snapshot
26
+ // React reconnects its subscribers (from the useSyncExternalStore) on its own
27
+ // and userland subscibers should basically always do the same anyway
28
+ // as each subscription should have its own cleanup logic and that should be called each such reconnect
29
+ ref.observers = new Set();
30
+ });
31
+ const systemSnapshot = actorRef.system.getSnapshot?.();
32
+ actorRef.stop();
33
+ actorRef.system._snapshot = systemSnapshot;
34
+ persistedSnapshots.forEach(([ref, snapshot]) => {
35
+ ref._processingStatus = 0;
36
+ ref._snapshot = snapshot;
37
+ });
17
38
  }
18
39
 
19
- function useIdleInterpreter(machine, options) {
20
- {
21
- const [initialMachine] = useState(machine);
22
- if (machine.config !== initialMachine.config) {
23
- console.warn(`Actor logic has changed between renders. This is not supported and may lead to invalid snapshots.`);
24
- }
25
- }
26
- const actorRef = useConstant(() => {
27
- return createActor(machine, options);
40
+ function useIdleActorRef(logic, options) {
41
+ let [[currentConfig, actorRef], setCurrent] = useState(() => {
42
+ const actorRef = createActor(logic, options);
43
+ return [logic.config, actorRef];
28
44
  });
45
+ if (logic.config !== currentConfig) {
46
+ const newActorRef = createActor(logic, {
47
+ ...options,
48
+ snapshot: actorRef.getPersistedSnapshot({
49
+ __unsafeAllowInlineActors: true
50
+ })
51
+ });
52
+ setCurrent([logic.config, newActorRef]);
53
+ actorRef = newActorRef;
54
+ }
29
55
 
30
56
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
31
57
  useIsomorphicLayoutEffect(() => {
32
- actorRef.logic.implementations = machine.implementations;
58
+ actorRef.logic.implementations = logic.implementations;
33
59
  });
34
60
  return actorRef;
35
61
  }
36
- function useActorRef(machine, ...[options = {}, observerOrListener]) {
37
- const actorRef = useIdleInterpreter(machine, options);
62
+ function useActorRef(machine, options = {}, observerOrListener) {
63
+ const actorRef = useIdleActorRef(machine, options);
38
64
  useEffect(() => {
39
65
  if (!observerOrListener) {
40
66
  return;
@@ -47,19 +73,17 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
47
73
  useEffect(() => {
48
74
  actorRef.start();
49
75
  return () => {
50
- actorRef.stop();
51
- actorRef.status = ActorStatus.NotStarted;
52
- actorRef._initState();
76
+ stopRootWithRehydration(actorRef);
53
77
  };
54
- }, []);
78
+ }, [actorRef]);
55
79
  return actorRef;
56
80
  }
57
81
 
58
82
  function useActor(logic, options = {}) {
59
- if (isActorRef(logic)) {
83
+ if (!!logic && 'send' in logic && typeof logic.send === 'function') {
60
84
  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.`);
61
85
  }
62
- const actorRef = useIdleInterpreter(logic, options);
86
+ const actorRef = useIdleActorRef(logic, options);
63
87
  const getSnapshot = useCallback(() => {
64
88
  return actorRef.getSnapshot();
65
89
  }, [actorRef]);
@@ -73,9 +97,7 @@ function useActor(logic, options = {}) {
73
97
  useEffect(() => {
74
98
  actorRef.start();
75
99
  return () => {
76
- actorRef.stop();
77
- actorRef.status = ActorStatus.NotStarted;
78
- actorRef._initState();
100
+ stopRootWithRehydration(actorRef);
79
101
  };
80
102
  }, [actorRef]);
81
103
  return [actorSnapshot, actorRef.send, actorRef];
@@ -160,8 +182,7 @@ function createActorContext(actorLogic, interpreterOptions) {
160
182
  }
161
183
 
162
184
  /**
163
- *
164
- * @deprecated Use `useActor(...)` instead.
185
+ * @alias useActor
165
186
  */
166
187
  function useMachine(machine, options = {}) {
167
188
  return useActor(machine, options);
@@ -1,34 +1,66 @@
1
1
  import * as React from 'react';
2
- import { useEffect, useCallback } from 'react';
2
+ import { useEffect, useState, useCallback } from 'react';
3
3
  import { useSyncExternalStore } from 'use-sync-external-store/shim';
4
- import { toObserver, ActorStatus, createActor } from 'xstate';
4
+ import { toObserver, createActor } from 'xstate';
5
5
  import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
6
- import 'xstate/actors';
7
6
  import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
8
7
 
9
- function useConstant(fn) {
10
- const ref = React.useRef();
11
- if (!ref.current) {
12
- ref.current = {
13
- v: fn()
14
- };
8
+ const forEachActor = (actorRef, callback) => {
9
+ callback(actorRef);
10
+ const children = actorRef.getSnapshot().children;
11
+ if (children) {
12
+ Object.values(children).forEach(child => {
13
+ forEachActor(child, callback);
14
+ });
15
15
  }
16
- return ref.current.v;
16
+ };
17
+ function stopRootWithRehydration(actorRef) {
18
+ // persist snapshot here in a custom way allows us to persist inline actors and to preserve actor references
19
+ // we do it to avoid setState in useEffect when the effect gets "reconnected"
20
+ // this currently only happens in Strict Effects but it simulates the Offscreen aka Activity API
21
+ // it also just allows us to end up with a somewhat more predictable behavior for the users
22
+ const persistedSnapshots = [];
23
+ forEachActor(actorRef, ref => {
24
+ persistedSnapshots.push([ref, ref.getSnapshot()]);
25
+ // muting observers allow us to avoid `useSelector` from being notified about the stopped snapshot
26
+ // React reconnects its subscribers (from the useSyncExternalStore) on its own
27
+ // and userland subscibers should basically always do the same anyway
28
+ // as each subscription should have its own cleanup logic and that should be called each such reconnect
29
+ ref.observers = new Set();
30
+ });
31
+ const systemSnapshot = actorRef.system.getSnapshot?.();
32
+ actorRef.stop();
33
+ actorRef.system._snapshot = systemSnapshot;
34
+ persistedSnapshots.forEach(([ref, snapshot]) => {
35
+ ref._processingStatus = 0;
36
+ ref._snapshot = snapshot;
37
+ });
17
38
  }
18
39
 
19
- function useIdleInterpreter(machine, options) {
20
- const actorRef = useConstant(() => {
21
- return createActor(machine, options);
40
+ function useIdleActorRef(logic, options) {
41
+ let [[currentConfig, actorRef], setCurrent] = useState(() => {
42
+ const actorRef = createActor(logic, options);
43
+ return [logic.config, actorRef];
22
44
  });
45
+ if (logic.config !== currentConfig) {
46
+ const newActorRef = createActor(logic, {
47
+ ...options,
48
+ snapshot: actorRef.getPersistedSnapshot({
49
+ __unsafeAllowInlineActors: true
50
+ })
51
+ });
52
+ setCurrent([logic.config, newActorRef]);
53
+ actorRef = newActorRef;
54
+ }
23
55
 
24
56
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
25
57
  useIsomorphicLayoutEffect(() => {
26
- actorRef.logic.implementations = machine.implementations;
58
+ actorRef.logic.implementations = logic.implementations;
27
59
  });
28
60
  return actorRef;
29
61
  }
30
- function useActorRef(machine, ...[options = {}, observerOrListener]) {
31
- const actorRef = useIdleInterpreter(machine, options);
62
+ function useActorRef(machine, options = {}, observerOrListener) {
63
+ const actorRef = useIdleActorRef(machine, options);
32
64
  useEffect(() => {
33
65
  if (!observerOrListener) {
34
66
  return;
@@ -41,16 +73,14 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
41
73
  useEffect(() => {
42
74
  actorRef.start();
43
75
  return () => {
44
- actorRef.stop();
45
- actorRef.status = ActorStatus.NotStarted;
46
- actorRef._initState();
76
+ stopRootWithRehydration(actorRef);
47
77
  };
48
- }, []);
78
+ }, [actorRef]);
49
79
  return actorRef;
50
80
  }
51
81
 
52
82
  function useActor(logic, options = {}) {
53
- const actorRef = useIdleInterpreter(logic, options);
83
+ const actorRef = useIdleActorRef(logic, options);
54
84
  const getSnapshot = useCallback(() => {
55
85
  return actorRef.getSnapshot();
56
86
  }, [actorRef]);
@@ -64,9 +94,7 @@ function useActor(logic, options = {}) {
64
94
  useEffect(() => {
65
95
  actorRef.start();
66
96
  return () => {
67
- actorRef.stop();
68
- actorRef.status = ActorStatus.NotStarted;
69
- actorRef._initState();
97
+ stopRootWithRehydration(actorRef);
70
98
  };
71
99
  }, [actorRef]);
72
100
  return [actorSnapshot, actorRef.send, actorRef];
@@ -151,8 +179,7 @@ function createActorContext(actorLogic, interpreterOptions) {
151
179
  }
152
180
 
153
181
  /**
154
- *
155
- * @deprecated Use `useActor(...)` instead.
182
+ * @alias useActor
156
183
  */
157
184
  function useMachine(machine, options = {}) {
158
185
  return useActor(machine, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xstate/react",
3
- "version": "4.0.0-beta.9",
3
+ "version": "4.0.1",
4
4
  "description": "XState tools for React",
5
5
  "keywords": [
6
6
  "state",
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "peerDependencies": {
57
57
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
58
- "xstate": "^5.0.0-beta.28"
58
+ "xstate": "^5.1.0"
59
59
  },
60
60
  "peerDependenciesMeta": {
61
61
  "xstate": {
@@ -76,6 +76,6 @@
76
76
  "jsdom-global": "^3.0.2",
77
77
  "react": "^18.0.0",
78
78
  "react-dom": "^18.0.0",
79
- "xstate": "5.0.0-beta.28"
79
+ "xstate": "5.1.0"
80
80
  }
81
81
  }