@xstate/react 4.0.0-beta.8 → 4.0.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
@@ -21,14 +21,6 @@ npm i xstate @xstate/react
21
21
 
22
22
  By using the global variable `XStateReact`
23
23
 
24
- or
25
-
26
- ```html
27
- <script src="https://unpkg.com/@xstate/react/dist/xstate-react-fsm.umd.min.js"></script>
28
- ```
29
-
30
- By using the global variable `XStateReactFSM`
31
-
32
24
  2. Import the `useMachine` hook:
33
25
 
34
26
  ```js
@@ -1,12 +1,16 @@
1
1
  import * as React from 'react';
2
- import { ActorRefFrom, AnyStateMachine, SnapshotFrom, InterpreterOptions, AreAllImplementationsAssumedToBeProvided, MarkAllImplementationsAsProvided, StateMachine, AnyActorLogic } from 'xstate';
3
- type ToMachinesWithProvidedImplementations<TMachine extends AnyStateMachine> = TMachine extends StateMachine<infer TContext, infer TEvent, infer TAction, infer TActorMap, infer TResolvedTypesMeta> ? StateMachine<TContext, TEvent, TAction, TActorMap, AreAllImplementationsAssumedToBeProvided<TResolvedTypesMeta> extends false ? MarkAllImplementationsAsProvided<TResolvedTypesMeta> : TResolvedTypesMeta> : never;
4
- export declare function createActorContext<TLogic extends AnyActorLogic>(actorLogic: TLogic, interpreterOptions?: InterpreterOptions<TLogic>): {
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 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
+ 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
- options?: InterpreterOptions<TLogic>;
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,2 +1,2 @@
1
- import { ActorRefFrom, AnyActorLogic, InterpreterOptions, SnapshotFrom } from 'xstate';
2
- export declare function useActor<TLogic extends AnyActorLogic>(logic: TLogic, options?: InterpreterOptions<TLogic>): [SnapshotFrom<TLogic>, ActorRefFrom<TLogic>['send'], ActorRefFrom<TLogic>];
1
+ import { ActorRefFrom, AnyActorLogic, ActorOptions, SnapshotFrom } from 'xstate';
2
+ export declare function useActor<TLogic extends AnyActorLogic>(logic: TLogic, options?: ActorOptions<TLogic>): [SnapshotFrom<TLogic>, ActorRefFrom<TLogic>['send'], ActorRefFrom<TLogic>];
@@ -1,14 +1,3 @@
1
- import { AnyActorLogic, AnyInterpreter, AnyStateMachine, AreAllImplementationsAssumedToBeProvided, InternalMachineImplementations, ActorRefFrom, InterpreterOptions, Observer, StateFrom, SnapshotFrom } from 'xstate';
2
- export declare function useIdleInterpreter(machine: AnyActorLogic, options: Partial<InterpreterOptions<AnyActorLogic>>): AnyInterpreter;
3
- type RestParams<TLogic extends AnyActorLogic> = TLogic extends AnyStateMachine ? AreAllImplementationsAssumedToBeProvided<TLogic['__TResolvedTypesMeta']> extends false ? [
4
- options: InterpreterOptions<TLogic> & InternalMachineImplementations<TLogic['__TContext'], TLogic['__TEvent'], TLogic['__TResolvedTypesMeta'], true>,
5
- observerOrListener?: Observer<StateFrom<TLogic>> | ((value: StateFrom<TLogic>) => void)
6
- ] : [
7
- options?: InterpreterOptions<TLogic> & InternalMachineImplementations<TLogic['__TContext'], TLogic['__TEvent'], TLogic['__TResolvedTypesMeta']>,
8
- observerOrListener?: Observer<StateFrom<TLogic>> | ((value: StateFrom<TLogic>) => void)
9
- ] : [
10
- options?: InterpreterOptions<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,9 +1,8 @@
1
- import { ActorRefFrom, AnyStateMachine, AreAllImplementationsAssumedToBeProvided, InterpreterOptions, MissingImplementationsError, StateFrom } from 'xstate';
1
+ import { ActorRefFrom, AnyStateMachine, AreAllImplementationsAssumedToBeProvided, ActorOptions, MissingImplementationsError, StateFrom } from 'xstate';
2
2
  /**
3
- *
4
- * @deprecated Use `useActor(...)` instead.
3
+ * @alias useActor
5
4
  */
6
- export declare function useMachine<TMachine extends AnyStateMachine>(machine: AreAllImplementationsAssumedToBeProvided<TMachine['__TResolvedTypesMeta']> extends true ? TMachine : MissingImplementationsError<TMachine['__TResolvedTypesMeta']>, options?: InterpreterOptions<TMachine>): [
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>,
8
7
  ActorRefFrom<TMachine>['send'],
9
8
  ActorRefFrom<TMachine>
@@ -5,9 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var React = require('react');
6
6
  var shim = require('use-sync-external-store/shim');
7
7
  var xstate = require('xstate');
8
- var useConstant = require('./useConstant-2ee82f84.cjs.js');
9
8
  var useIsomorphicLayoutEffect = require('use-isomorphic-layout-effect');
10
- require('xstate/actors');
11
9
  var withSelector = require('use-sync-external-store/shim/with-selector');
12
10
 
13
11
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
@@ -33,19 +31,60 @@ function _interopNamespace(e) {
33
31
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
34
32
  var useIsomorphicLayoutEffect__default = /*#__PURE__*/_interopDefault(useIsomorphicLayoutEffect);
35
33
 
36
- function useIdleInterpreter(machine, options) {
37
- const actorRef = useConstant.useConstant(() => {
38
- return xstate.interpret(machine, options);
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
+ }
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
+ actorRef.stop();
58
+ persistedSnapshots.forEach(([ref, snapshot]) => {
59
+ ref._processingStatus = 0;
60
+ ref._snapshot = snapshot;
39
61
  });
62
+ }
63
+
64
+ function useIdleActorRef(logic, options) {
65
+ let [[currentConfig, actorRef], setCurrent] = React.useState(() => {
66
+ const actorRef = xstate.createActor(logic, options);
67
+ return [logic.config, actorRef];
68
+ });
69
+ if (logic.config !== currentConfig) {
70
+ const newActorRef = xstate.createActor(logic, {
71
+ ...options,
72
+ snapshot: actorRef.getPersistedSnapshot({
73
+ __unsafeAllowInlineActors: true
74
+ })
75
+ });
76
+ setCurrent([logic.config, newActorRef]);
77
+ actorRef = newActorRef;
78
+ }
40
79
 
41
80
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
42
81
  useIsomorphicLayoutEffect__default["default"](() => {
43
- actorRef.logic.implementations = machine.implementations;
82
+ actorRef.logic.implementations = logic.implementations;
44
83
  });
45
84
  return actorRef;
46
85
  }
47
- function useActorRef(machine, ...[options = {}, observerOrListener]) {
48
- const actorRef = useIdleInterpreter(machine, options);
86
+ function useActorRef(machine, options = {}, observerOrListener) {
87
+ const actorRef = useIdleActorRef(machine, options);
49
88
  React.useEffect(() => {
50
89
  if (!observerOrListener) {
51
90
  return;
@@ -58,16 +97,14 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
58
97
  React.useEffect(() => {
59
98
  actorRef.start();
60
99
  return () => {
61
- actorRef.stop();
62
- actorRef.status = xstate.InterpreterStatus.NotStarted;
63
- actorRef._initState();
100
+ stopRootWithRehydration(actorRef);
64
101
  };
65
- }, []);
102
+ }, [actorRef]);
66
103
  return actorRef;
67
104
  }
68
105
 
69
106
  function useActor(logic, options = {}) {
70
- const actorRef = useIdleInterpreter(logic, options);
107
+ const actorRef = useIdleActorRef(logic, options);
71
108
  const getSnapshot = React.useCallback(() => {
72
109
  return actorRef.getSnapshot();
73
110
  }, [actorRef]);
@@ -81,9 +118,7 @@ function useActor(logic, options = {}) {
81
118
  React.useEffect(() => {
82
119
  actorRef.start();
83
120
  return () => {
84
- actorRef.stop();
85
- actorRef.status = xstate.InterpreterStatus.NotStarted;
86
- actorRef._initState();
121
+ stopRootWithRehydration(actorRef);
87
122
  };
88
123
  }, [actorRef]);
89
124
  return [actorSnapshot, actorRef.send, actorRef];
@@ -168,8 +203,7 @@ function createActorContext(actorLogic, interpreterOptions) {
168
203
  }
169
204
 
170
205
  /**
171
- *
172
- * @deprecated Use `useActor(...)` instead.
206
+ * @alias useActor
173
207
  */
174
208
  function useMachine(machine, options = {}) {
175
209
  return useActor(machine, options);
@@ -5,9 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var React = require('react');
6
6
  var shim = require('use-sync-external-store/shim');
7
7
  var xstate = require('xstate');
8
- var useConstant = require('./useConstant-ae6dceac.development.cjs.js');
9
8
  var useIsomorphicLayoutEffect = require('use-isomorphic-layout-effect');
10
- var actors = require('xstate/actors');
11
9
  var withSelector = require('use-sync-external-store/shim/with-selector');
12
10
 
13
11
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
@@ -33,25 +31,60 @@ function _interopNamespace(e) {
33
31
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
34
32
  var useIsomorphicLayoutEffect__default = /*#__PURE__*/_interopDefault(useIsomorphicLayoutEffect);
35
33
 
36
- function useIdleInterpreter(machine, options) {
37
- {
38
- const [initialMachine] = React.useState(machine);
39
- if (machine.config !== initialMachine.config) {
40
- console.warn(`Actor logic has changed between renders. This is not supported and may lead to invalid snapshots.`);
41
- }
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
+ });
42
41
  }
43
- const actorRef = useConstant.useConstant(() => {
44
- return xstate.interpret(machine, options);
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
+ actorRef.stop();
58
+ persistedSnapshots.forEach(([ref, snapshot]) => {
59
+ ref._processingStatus = 0;
60
+ ref._snapshot = snapshot;
45
61
  });
62
+ }
63
+
64
+ function useIdleActorRef(logic, options) {
65
+ let [[currentConfig, actorRef], setCurrent] = React.useState(() => {
66
+ const actorRef = xstate.createActor(logic, options);
67
+ return [logic.config, actorRef];
68
+ });
69
+ if (logic.config !== currentConfig) {
70
+ const newActorRef = xstate.createActor(logic, {
71
+ ...options,
72
+ snapshot: actorRef.getPersistedSnapshot({
73
+ __unsafeAllowInlineActors: true
74
+ })
75
+ });
76
+ setCurrent([logic.config, newActorRef]);
77
+ actorRef = newActorRef;
78
+ }
46
79
 
47
80
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
48
81
  useIsomorphicLayoutEffect__default["default"](() => {
49
- actorRef.logic.implementations = machine.implementations;
82
+ actorRef.logic.implementations = logic.implementations;
50
83
  });
51
84
  return actorRef;
52
85
  }
53
- function useActorRef(machine, ...[options = {}, observerOrListener]) {
54
- const actorRef = useIdleInterpreter(machine, options);
86
+ function useActorRef(machine, options = {}, observerOrListener) {
87
+ const actorRef = useIdleActorRef(machine, options);
55
88
  React.useEffect(() => {
56
89
  if (!observerOrListener) {
57
90
  return;
@@ -64,19 +97,17 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
64
97
  React.useEffect(() => {
65
98
  actorRef.start();
66
99
  return () => {
67
- actorRef.stop();
68
- actorRef.status = xstate.InterpreterStatus.NotStarted;
69
- actorRef._initState();
100
+ stopRootWithRehydration(actorRef);
70
101
  };
71
- }, []);
102
+ }, [actorRef]);
72
103
  return actorRef;
73
104
  }
74
105
 
75
106
  function useActor(logic, options = {}) {
76
- if (actors.isActorRef(logic)) {
107
+ if (!!logic && 'send' in logic && typeof logic.send === 'function') {
77
108
  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.`);
78
109
  }
79
- const actorRef = useIdleInterpreter(logic, options);
110
+ const actorRef = useIdleActorRef(logic, options);
80
111
  const getSnapshot = React.useCallback(() => {
81
112
  return actorRef.getSnapshot();
82
113
  }, [actorRef]);
@@ -90,9 +121,7 @@ function useActor(logic, options = {}) {
90
121
  React.useEffect(() => {
91
122
  actorRef.start();
92
123
  return () => {
93
- actorRef.stop();
94
- actorRef.status = xstate.InterpreterStatus.NotStarted;
95
- actorRef._initState();
124
+ stopRootWithRehydration(actorRef);
96
125
  };
97
126
  }, [actorRef]);
98
127
  return [actorSnapshot, actorRef.send, actorRef];
@@ -177,8 +206,7 @@ function createActorContext(actorLogic, interpreterOptions) {
177
206
  }
178
207
 
179
208
  /**
180
- *
181
- * @deprecated Use `useActor(...)` instead.
209
+ * @alias useActor
182
210
  */
183
211
  function useMachine(machine, options = {}) {
184
212
  return useActor(machine, options);
@@ -1,31 +1,64 @@
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, InterpreterStatus, interpret } from 'xstate';
5
- import { u as useConstant } from './useConstant-bac83df4.development.esm.js';
4
+ import { toObserver, createActor } from 'xstate';
6
5
  import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
7
- import { isActorRef } from 'xstate/actors';
8
6
  import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
9
7
 
10
- function useIdleInterpreter(machine, options) {
11
- {
12
- const [initialMachine] = useState(machine);
13
- if (machine.config !== initialMachine.config) {
14
- console.warn(`Actor logic has changed between renders. This is not supported and may lead to invalid snapshots.`);
15
- }
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
+ });
16
15
  }
17
- const actorRef = useConstant(() => {
18
- return interpret(machine, options);
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
+ actorRef.stop();
32
+ persistedSnapshots.forEach(([ref, snapshot]) => {
33
+ ref._processingStatus = 0;
34
+ ref._snapshot = snapshot;
19
35
  });
36
+ }
37
+
38
+ function useIdleActorRef(logic, options) {
39
+ let [[currentConfig, actorRef], setCurrent] = useState(() => {
40
+ const actorRef = createActor(logic, options);
41
+ return [logic.config, actorRef];
42
+ });
43
+ if (logic.config !== currentConfig) {
44
+ const newActorRef = createActor(logic, {
45
+ ...options,
46
+ snapshot: actorRef.getPersistedSnapshot({
47
+ __unsafeAllowInlineActors: true
48
+ })
49
+ });
50
+ setCurrent([logic.config, newActorRef]);
51
+ actorRef = newActorRef;
52
+ }
20
53
 
21
54
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
22
55
  useIsomorphicLayoutEffect(() => {
23
- actorRef.logic.implementations = machine.implementations;
56
+ actorRef.logic.implementations = logic.implementations;
24
57
  });
25
58
  return actorRef;
26
59
  }
27
- function useActorRef(machine, ...[options = {}, observerOrListener]) {
28
- const actorRef = useIdleInterpreter(machine, options);
60
+ function useActorRef(machine, options = {}, observerOrListener) {
61
+ const actorRef = useIdleActorRef(machine, options);
29
62
  useEffect(() => {
30
63
  if (!observerOrListener) {
31
64
  return;
@@ -38,19 +71,17 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
38
71
  useEffect(() => {
39
72
  actorRef.start();
40
73
  return () => {
41
- actorRef.stop();
42
- actorRef.status = InterpreterStatus.NotStarted;
43
- actorRef._initState();
74
+ stopRootWithRehydration(actorRef);
44
75
  };
45
- }, []);
76
+ }, [actorRef]);
46
77
  return actorRef;
47
78
  }
48
79
 
49
80
  function useActor(logic, options = {}) {
50
- if (isActorRef(logic)) {
81
+ if (!!logic && 'send' in logic && typeof logic.send === 'function') {
51
82
  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.`);
52
83
  }
53
- const actorRef = useIdleInterpreter(logic, options);
84
+ const actorRef = useIdleActorRef(logic, options);
54
85
  const getSnapshot = useCallback(() => {
55
86
  return actorRef.getSnapshot();
56
87
  }, [actorRef]);
@@ -64,9 +95,7 @@ function useActor(logic, options = {}) {
64
95
  useEffect(() => {
65
96
  actorRef.start();
66
97
  return () => {
67
- actorRef.stop();
68
- actorRef.status = InterpreterStatus.NotStarted;
69
- actorRef._initState();
98
+ stopRootWithRehydration(actorRef);
70
99
  };
71
100
  }, [actorRef]);
72
101
  return [actorSnapshot, actorRef.send, actorRef];
@@ -151,8 +180,7 @@ function createActorContext(actorLogic, interpreterOptions) {
151
180
  }
152
181
 
153
182
  /**
154
- *
155
- * @deprecated Use `useActor(...)` instead.
183
+ * @alias useActor
156
184
  */
157
185
  function useMachine(machine, options = {}) {
158
186
  return useActor(machine, options);
@@ -1,25 +1,64 @@
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, InterpreterStatus, interpret } from 'xstate';
5
- import { u as useConstant } from './useConstant-c7ec0fdd.esm.js';
4
+ import { toObserver, createActor } from 'xstate';
6
5
  import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
7
- import 'xstate/actors';
8
6
  import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
9
7
 
10
- function useIdleInterpreter(machine, options) {
11
- const actorRef = useConstant(() => {
12
- return interpret(machine, options);
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
+ }
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
+ actorRef.stop();
32
+ persistedSnapshots.forEach(([ref, snapshot]) => {
33
+ ref._processingStatus = 0;
34
+ ref._snapshot = snapshot;
13
35
  });
36
+ }
37
+
38
+ function useIdleActorRef(logic, options) {
39
+ let [[currentConfig, actorRef], setCurrent] = useState(() => {
40
+ const actorRef = createActor(logic, options);
41
+ return [logic.config, actorRef];
42
+ });
43
+ if (logic.config !== currentConfig) {
44
+ const newActorRef = createActor(logic, {
45
+ ...options,
46
+ snapshot: actorRef.getPersistedSnapshot({
47
+ __unsafeAllowInlineActors: true
48
+ })
49
+ });
50
+ setCurrent([logic.config, newActorRef]);
51
+ actorRef = newActorRef;
52
+ }
14
53
 
15
54
  // TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
16
55
  useIsomorphicLayoutEffect(() => {
17
- actorRef.logic.implementations = machine.implementations;
56
+ actorRef.logic.implementations = logic.implementations;
18
57
  });
19
58
  return actorRef;
20
59
  }
21
- function useActorRef(machine, ...[options = {}, observerOrListener]) {
22
- const actorRef = useIdleInterpreter(machine, options);
60
+ function useActorRef(machine, options = {}, observerOrListener) {
61
+ const actorRef = useIdleActorRef(machine, options);
23
62
  useEffect(() => {
24
63
  if (!observerOrListener) {
25
64
  return;
@@ -32,16 +71,14 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
32
71
  useEffect(() => {
33
72
  actorRef.start();
34
73
  return () => {
35
- actorRef.stop();
36
- actorRef.status = InterpreterStatus.NotStarted;
37
- actorRef._initState();
74
+ stopRootWithRehydration(actorRef);
38
75
  };
39
- }, []);
76
+ }, [actorRef]);
40
77
  return actorRef;
41
78
  }
42
79
 
43
80
  function useActor(logic, options = {}) {
44
- const actorRef = useIdleInterpreter(logic, options);
81
+ const actorRef = useIdleActorRef(logic, options);
45
82
  const getSnapshot = useCallback(() => {
46
83
  return actorRef.getSnapshot();
47
84
  }, [actorRef]);
@@ -55,9 +92,7 @@ function useActor(logic, options = {}) {
55
92
  useEffect(() => {
56
93
  actorRef.start();
57
94
  return () => {
58
- actorRef.stop();
59
- actorRef.status = InterpreterStatus.NotStarted;
60
- actorRef._initState();
95
+ stopRootWithRehydration(actorRef);
61
96
  };
62
97
  }, [actorRef]);
63
98
  return [actorSnapshot, actorRef.send, actorRef];
@@ -142,8 +177,7 @@ function createActorContext(actorLogic, interpreterOptions) {
142
177
  }
143
178
 
144
179
  /**
145
- *
146
- * @deprecated Use `useActor(...)` instead.
180
+ * @alias useActor
147
181
  */
148
182
  function useMachine(machine, options = {}) {
149
183
  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.8",
3
+ "version": "4.0.0",
4
4
  "description": "XState tools for React",
5
5
  "keywords": [
6
6
  "state",
@@ -18,20 +18,6 @@
18
18
  "main": "dist/xstate-react.cjs.js",
19
19
  "module": "dist/xstate-react.esm.js",
20
20
  "exports": {
21
- "./fsm": {
22
- "types": {
23
- "import": "./fsm/dist/xstate-react-fsm.cjs.mjs",
24
- "default": "./fsm/dist/xstate-react-fsm.cjs.js"
25
- },
26
- "development": {
27
- "module": "./fsm/dist/xstate-react-fsm.development.esm.js",
28
- "import": "./fsm/dist/xstate-react-fsm.development.cjs.mjs",
29
- "default": "./fsm/dist/xstate-react-fsm.development.cjs.js"
30
- },
31
- "module": "./fsm/dist/xstate-react-fsm.esm.js",
32
- "import": "./fsm/dist/xstate-react-fsm.cjs.mjs",
33
- "default": "./fsm/dist/xstate-react-fsm.cjs.js"
34
- },
35
21
  ".": {
36
22
  "types": {
37
23
  "import": "./dist/xstate-react.cjs.mjs",
@@ -57,8 +43,7 @@
57
43
  "types": "dist/xstate-react.cjs.d.ts",
58
44
  "sideEffects": false,
59
45
  "files": [
60
- "dist",
61
- "fsm"
46
+ "dist"
62
47
  ],
63
48
  "repository": {
64
49
  "type": "git",
@@ -69,14 +54,10 @@
69
54
  "url": "https://github.com/statelyai/xstate/issues"
70
55
  },
71
56
  "peerDependencies": {
72
- "@xstate/fsm": "^3.0.0-beta.3",
73
57
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
74
- "xstate": "^5.0.0-beta.18"
58
+ "xstate": "^5.0.0"
75
59
  },
76
60
  "peerDependenciesMeta": {
77
- "@xstate/fsm": {
78
- "optional": true
79
- },
80
61
  "xstate": {
81
62
  "optional": true
82
63
  }
@@ -91,17 +72,10 @@
91
72
  "@types/react": "^17.0.43",
92
73
  "@types/react-dom": "^17.0.14",
93
74
  "@types/use-sync-external-store": "^0.0.3",
94
- "@xstate/fsm": "3.0.0-beta.3",
95
75
  "jsdom": "^14.0.0",
96
76
  "jsdom-global": "^3.0.2",
97
77
  "react": "^18.0.0",
98
78
  "react-dom": "^18.0.0",
99
- "xstate": "5.0.0-beta.18"
100
- },
101
- "preconstruct": {
102
- "entrypoints": [
103
- "./index.ts",
104
- "./fsm.ts"
105
- ]
79
+ "xstate": "5.0.0"
106
80
  }
107
81
  }
@@ -1,3 +0,0 @@
1
- import { MachineImplementationsFrom, ServiceFrom, StateFrom, StateMachine } from '@xstate/fsm';
2
- export declare function useMachine<TMachine extends StateMachine.AnyMachine>(stateMachine: TMachine, options?: MachineImplementationsFrom<TMachine>): [StateFrom<TMachine>, ServiceFrom<TMachine>['send'], ServiceFrom<TMachine>];
3
- export declare function useService<TService extends StateMachine.AnyService>(service: TService): [StateFrom<TService>, TService['send'], TService];
@@ -1,35 +0,0 @@
1
- 'use strict';
2
-
3
- var React = require('react');
4
-
5
- function _interopNamespace(e) {
6
- if (e && e.__esModule) return e;
7
- var n = Object.create(null);
8
- if (e) {
9
- Object.keys(e).forEach(function (k) {
10
- if (k !== 'default') {
11
- var d = Object.getOwnPropertyDescriptor(e, k);
12
- Object.defineProperty(n, k, d.get ? d : {
13
- enumerable: true,
14
- get: function () { return e[k]; }
15
- });
16
- }
17
- });
18
- }
19
- n["default"] = e;
20
- return Object.freeze(n);
21
- }
22
-
23
- var React__namespace = /*#__PURE__*/_interopNamespace(React);
24
-
25
- function useConstant(fn) {
26
- const ref = React__namespace.useRef();
27
- if (!ref.current) {
28
- ref.current = {
29
- v: fn()
30
- };
31
- }
32
- return ref.current.v;
33
- }
34
-
35
- exports.useConstant = useConstant;
@@ -1,35 +0,0 @@
1
- 'use strict';
2
-
3
- var React = require('react');
4
-
5
- function _interopNamespace(e) {
6
- if (e && e.__esModule) return e;
7
- var n = Object.create(null);
8
- if (e) {
9
- Object.keys(e).forEach(function (k) {
10
- if (k !== 'default') {
11
- var d = Object.getOwnPropertyDescriptor(e, k);
12
- Object.defineProperty(n, k, d.get ? d : {
13
- enumerable: true,
14
- get: function () { return e[k]; }
15
- });
16
- }
17
- });
18
- }
19
- n["default"] = e;
20
- return Object.freeze(n);
21
- }
22
-
23
- var React__namespace = /*#__PURE__*/_interopNamespace(React);
24
-
25
- function useConstant(fn) {
26
- const ref = React__namespace.useRef();
27
- if (!ref.current) {
28
- ref.current = {
29
- v: fn()
30
- };
31
- }
32
- return ref.current.v;
33
- }
34
-
35
- exports.useConstant = useConstant;
@@ -1,13 +0,0 @@
1
- import * as React from 'react';
2
-
3
- function useConstant(fn) {
4
- const ref = React.useRef();
5
- if (!ref.current) {
6
- ref.current = {
7
- v: fn()
8
- };
9
- }
10
- return ref.current.v;
11
- }
12
-
13
- export { useConstant as u };
@@ -1,13 +0,0 @@
1
- import * as React from 'react';
2
-
3
- function useConstant(fn) {
4
- const ref = React.useRef();
5
- if (!ref.current) {
6
- ref.current = {
7
- v: fn()
8
- };
9
- }
10
- return ref.current.v;
11
- }
12
-
13
- export { useConstant as u };
@@ -1,2 +0,0 @@
1
- export * from "../../dist/declarations/src/fsm.js";
2
- //# sourceMappingURL=xstate-react-fsm.cjs.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"xstate-react-fsm.cjs.d.mts","sourceRoot":"","sources":["../../dist/declarations/src/fsm.d.ts"],"names":[],"mappings":"AAAA"}
@@ -1,2 +0,0 @@
1
- export * from "../../dist/declarations/src/fsm";
2
- //# sourceMappingURL=xstate-react-fsm.cjs.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"xstate-react-fsm.cjs.d.ts","sourceRoot":"","sources":["../../dist/declarations/src/fsm.d.ts"],"names":[],"mappings":"AAAA"}
@@ -1,68 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var fsm = require('@xstate/fsm');
6
- var React = require('react');
7
- var useIsomorphicLayoutEffect = require('use-isomorphic-layout-effect');
8
- var withSelector = require('use-sync-external-store/shim/with-selector');
9
- var useConstant = require('../../dist/useConstant-2ee82f84.cjs.js');
10
-
11
- function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
12
-
13
- var useIsomorphicLayoutEffect__default = /*#__PURE__*/_interopDefault(useIsomorphicLayoutEffect);
14
-
15
- function identity(a) {
16
- return a;
17
- }
18
- function useMachine(stateMachine, options) {
19
- const persistedStateRef = React.useRef();
20
- const [service, queue] = useConstant.useConstant(() => {
21
- const queue = [];
22
- const service = fsm.interpret(fsm.createMachine(stateMachine.config, options ? options : stateMachine._options));
23
- const {
24
- send
25
- } = service;
26
- service.send = event => {
27
- if (service.status === fsm.InterpreterStatus.NotStarted) {
28
- queue.push(event);
29
- return;
30
- }
31
- send(event);
32
- persistedStateRef.current = service.state;
33
- };
34
- return [service, queue];
35
- });
36
-
37
- // TODO: consider using `useInsertionEffect` if available
38
- useIsomorphicLayoutEffect__default["default"](() => {
39
- if (options) {
40
- service._machine._options = options;
41
- }
42
- });
43
- const useServiceResult = useService(service);
44
- React.useEffect(() => {
45
- service.start(persistedStateRef.current);
46
- queue.forEach(service.send);
47
- persistedStateRef.current = service.state;
48
- return () => {
49
- service.stop();
50
- };
51
- }, []);
52
- return useServiceResult;
53
- }
54
- const isEqual = (_prevState, nextState) => nextState.changed === false;
55
- function useService(service) {
56
- const getSnapshot = React.useCallback(() => service.state, [service]);
57
- const subscribe = React.useCallback(handleStoreChange => {
58
- const {
59
- unsubscribe
60
- } = service.subscribe(handleStoreChange);
61
- return unsubscribe;
62
- }, [service]);
63
- const storeSnapshot = withSelector.useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity, isEqual);
64
- return [storeSnapshot, service.send, service];
65
- }
66
-
67
- exports.useMachine = useMachine;
68
- exports.useService = useService;
@@ -1,4 +0,0 @@
1
- export {
2
- useMachine,
3
- useService
4
- } from "./xstate-react-fsm.cjs.js";
@@ -1,74 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var fsm = require('@xstate/fsm');
6
- var React = require('react');
7
- var useIsomorphicLayoutEffect = require('use-isomorphic-layout-effect');
8
- var withSelector = require('use-sync-external-store/shim/with-selector');
9
- var useConstant = require('../../dist/useConstant-ae6dceac.development.cjs.js');
10
-
11
- function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
12
-
13
- var useIsomorphicLayoutEffect__default = /*#__PURE__*/_interopDefault(useIsomorphicLayoutEffect);
14
-
15
- function identity(a) {
16
- return a;
17
- }
18
- function useMachine(stateMachine, options) {
19
- const persistedStateRef = React.useRef();
20
- {
21
- const [initialMachine] = React.useState(stateMachine);
22
- if (stateMachine !== initialMachine) {
23
- console.warn('Machine given to `useMachine` has changed between renders. This is not supported and might lead to unexpected results.\n' + 'Please make sure that you pass the same Machine as argument each time.');
24
- }
25
- }
26
- const [service, queue] = useConstant.useConstant(() => {
27
- const queue = [];
28
- const service = fsm.interpret(fsm.createMachine(stateMachine.config, options ? options : stateMachine._options));
29
- const {
30
- send
31
- } = service;
32
- service.send = event => {
33
- if (service.status === fsm.InterpreterStatus.NotStarted) {
34
- queue.push(event);
35
- return;
36
- }
37
- send(event);
38
- persistedStateRef.current = service.state;
39
- };
40
- return [service, queue];
41
- });
42
-
43
- // TODO: consider using `useInsertionEffect` if available
44
- useIsomorphicLayoutEffect__default["default"](() => {
45
- if (options) {
46
- service._machine._options = options;
47
- }
48
- });
49
- const useServiceResult = useService(service);
50
- React.useEffect(() => {
51
- service.start(persistedStateRef.current);
52
- queue.forEach(service.send);
53
- persistedStateRef.current = service.state;
54
- return () => {
55
- service.stop();
56
- };
57
- }, []);
58
- return useServiceResult;
59
- }
60
- const isEqual = (_prevState, nextState) => nextState.changed === false;
61
- function useService(service) {
62
- const getSnapshot = React.useCallback(() => service.state, [service]);
63
- const subscribe = React.useCallback(handleStoreChange => {
64
- const {
65
- unsubscribe
66
- } = service.subscribe(handleStoreChange);
67
- return unsubscribe;
68
- }, [service]);
69
- const storeSnapshot = withSelector.useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity, isEqual);
70
- return [storeSnapshot, service.send, service];
71
- }
72
-
73
- exports.useMachine = useMachine;
74
- exports.useService = useService;
@@ -1,4 +0,0 @@
1
- export {
2
- useMachine,
3
- useService
4
- } from "./xstate-react-fsm.development.cjs.js";
@@ -1,65 +0,0 @@
1
- import { interpret, createMachine, InterpreterStatus } from '@xstate/fsm';
2
- import { useRef, useState, useEffect, useCallback } from 'react';
3
- import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
4
- import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
5
- import { u as useConstant } from '../../dist/useConstant-bac83df4.development.esm.js';
6
-
7
- function identity(a) {
8
- return a;
9
- }
10
- function useMachine(stateMachine, options) {
11
- const persistedStateRef = useRef();
12
- {
13
- const [initialMachine] = useState(stateMachine);
14
- if (stateMachine !== initialMachine) {
15
- console.warn('Machine given to `useMachine` has changed between renders. This is not supported and might lead to unexpected results.\n' + 'Please make sure that you pass the same Machine as argument each time.');
16
- }
17
- }
18
- const [service, queue] = useConstant(() => {
19
- const queue = [];
20
- const service = interpret(createMachine(stateMachine.config, options ? options : stateMachine._options));
21
- const {
22
- send
23
- } = service;
24
- service.send = event => {
25
- if (service.status === InterpreterStatus.NotStarted) {
26
- queue.push(event);
27
- return;
28
- }
29
- send(event);
30
- persistedStateRef.current = service.state;
31
- };
32
- return [service, queue];
33
- });
34
-
35
- // TODO: consider using `useInsertionEffect` if available
36
- useIsomorphicLayoutEffect(() => {
37
- if (options) {
38
- service._machine._options = options;
39
- }
40
- });
41
- const useServiceResult = useService(service);
42
- useEffect(() => {
43
- service.start(persistedStateRef.current);
44
- queue.forEach(service.send);
45
- persistedStateRef.current = service.state;
46
- return () => {
47
- service.stop();
48
- };
49
- }, []);
50
- return useServiceResult;
51
- }
52
- const isEqual = (_prevState, nextState) => nextState.changed === false;
53
- function useService(service) {
54
- const getSnapshot = useCallback(() => service.state, [service]);
55
- const subscribe = useCallback(handleStoreChange => {
56
- const {
57
- unsubscribe
58
- } = service.subscribe(handleStoreChange);
59
- return unsubscribe;
60
- }, [service]);
61
- const storeSnapshot = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity, isEqual);
62
- return [storeSnapshot, service.send, service];
63
- }
64
-
65
- export { useMachine, useService };
@@ -1,59 +0,0 @@
1
- import { interpret, createMachine, InterpreterStatus } from '@xstate/fsm';
2
- import { useRef, useEffect, useCallback } from 'react';
3
- import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
4
- import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
5
- import { u as useConstant } from '../../dist/useConstant-c7ec0fdd.esm.js';
6
-
7
- function identity(a) {
8
- return a;
9
- }
10
- function useMachine(stateMachine, options) {
11
- const persistedStateRef = useRef();
12
- const [service, queue] = useConstant(() => {
13
- const queue = [];
14
- const service = interpret(createMachine(stateMachine.config, options ? options : stateMachine._options));
15
- const {
16
- send
17
- } = service;
18
- service.send = event => {
19
- if (service.status === InterpreterStatus.NotStarted) {
20
- queue.push(event);
21
- return;
22
- }
23
- send(event);
24
- persistedStateRef.current = service.state;
25
- };
26
- return [service, queue];
27
- });
28
-
29
- // TODO: consider using `useInsertionEffect` if available
30
- useIsomorphicLayoutEffect(() => {
31
- if (options) {
32
- service._machine._options = options;
33
- }
34
- });
35
- const useServiceResult = useService(service);
36
- useEffect(() => {
37
- service.start(persistedStateRef.current);
38
- queue.forEach(service.send);
39
- persistedStateRef.current = service.state;
40
- return () => {
41
- service.stop();
42
- };
43
- }, []);
44
- return useServiceResult;
45
- }
46
- const isEqual = (_prevState, nextState) => nextState.changed === false;
47
- function useService(service) {
48
- const getSnapshot = useCallback(() => service.state, [service]);
49
- const subscribe = useCallback(handleStoreChange => {
50
- const {
51
- unsubscribe
52
- } = service.subscribe(handleStoreChange);
53
- return unsubscribe;
54
- }, [service]);
55
- const storeSnapshot = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity, isEqual);
56
- return [storeSnapshot, service.send, service];
57
- }
58
-
59
- export { useMachine, useService };
package/fsm/package.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "main": "dist/xstate-react-fsm.cjs.js",
3
- "module": "dist/xstate-react-fsm.esm.js"
4
- }