@xstate/react 4.0.0-beta.9 → 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/dist/declarations/src/createActorContext.d.ts +5 -1
- package/dist/declarations/src/useActorRef.d.ts +3 -14
- package/dist/declarations/src/useMachine.d.ts +1 -2
- package/dist/xstate-react.cjs.js +49 -24
- package/dist/xstate-react.development.cjs.js +50 -31
- package/dist/xstate-react.development.esm.js +51 -32
- package/dist/xstate-react.esm.js +51 -26
- package/package.json +3 -3
|
@@ -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,
|
|
2
|
-
export declare function
|
|
3
|
-
|
|
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>,
|
package/dist/xstate-react.cjs.js
CHANGED
|
@@ -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,60 @@ function _interopNamespace(e) {
|
|
|
32
31
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
33
32
|
var useIsomorphicLayoutEffect__default = /*#__PURE__*/_interopDefault(useIsomorphicLayoutEffect);
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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;
|
|
61
|
+
});
|
|
43
62
|
}
|
|
44
63
|
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
64
|
+
function useIdleActorRef(logic, options) {
|
|
65
|
+
let [[currentConfig, actorRef], setCurrent] = React.useState(() => {
|
|
66
|
+
const actorRef = xstate.createActor(logic, options);
|
|
67
|
+
return [logic.config, actorRef];
|
|
48
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
|
+
}
|
|
49
79
|
|
|
50
80
|
// TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
|
|
51
81
|
useIsomorphicLayoutEffect__default["default"](() => {
|
|
52
|
-
actorRef.logic.implementations =
|
|
82
|
+
actorRef.logic.implementations = logic.implementations;
|
|
53
83
|
});
|
|
54
84
|
return actorRef;
|
|
55
85
|
}
|
|
56
|
-
function useActorRef(machine,
|
|
57
|
-
const actorRef =
|
|
86
|
+
function useActorRef(machine, options = {}, observerOrListener) {
|
|
87
|
+
const actorRef = useIdleActorRef(machine, options);
|
|
58
88
|
React.useEffect(() => {
|
|
59
89
|
if (!observerOrListener) {
|
|
60
90
|
return;
|
|
@@ -67,16 +97,14 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
|
|
|
67
97
|
React.useEffect(() => {
|
|
68
98
|
actorRef.start();
|
|
69
99
|
return () => {
|
|
70
|
-
actorRef
|
|
71
|
-
actorRef.status = xstate.ActorStatus.NotStarted;
|
|
72
|
-
actorRef._initState();
|
|
100
|
+
stopRootWithRehydration(actorRef);
|
|
73
101
|
};
|
|
74
|
-
}, []);
|
|
102
|
+
}, [actorRef]);
|
|
75
103
|
return actorRef;
|
|
76
104
|
}
|
|
77
105
|
|
|
78
106
|
function useActor(logic, options = {}) {
|
|
79
|
-
const actorRef =
|
|
107
|
+
const actorRef = useIdleActorRef(logic, options);
|
|
80
108
|
const getSnapshot = React.useCallback(() => {
|
|
81
109
|
return actorRef.getSnapshot();
|
|
82
110
|
}, [actorRef]);
|
|
@@ -90,9 +118,7 @@ function useActor(logic, options = {}) {
|
|
|
90
118
|
React.useEffect(() => {
|
|
91
119
|
actorRef.start();
|
|
92
120
|
return () => {
|
|
93
|
-
actorRef
|
|
94
|
-
actorRef.status = xstate.ActorStatus.NotStarted;
|
|
95
|
-
actorRef._initState();
|
|
121
|
+
stopRootWithRehydration(actorRef);
|
|
96
122
|
};
|
|
97
123
|
}, [actorRef]);
|
|
98
124
|
return [actorSnapshot, actorRef.send, actorRef];
|
|
@@ -177,8 +203,7 @@ function createActorContext(actorLogic, interpreterOptions) {
|
|
|
177
203
|
}
|
|
178
204
|
|
|
179
205
|
/**
|
|
180
|
-
*
|
|
181
|
-
* @deprecated Use `useActor(...)` instead.
|
|
206
|
+
* @alias useActor
|
|
182
207
|
*/
|
|
183
208
|
function useMachine(machine, options = {}) {
|
|
184
209
|
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,60 @@ function _interopNamespace(e) {
|
|
|
32
31
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
33
32
|
var useIsomorphicLayoutEffect__default = /*#__PURE__*/_interopDefault(useIsomorphicLayoutEffect);
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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;
|
|
61
|
+
});
|
|
43
62
|
}
|
|
44
63
|
|
|
45
|
-
function
|
|
46
|
-
{
|
|
47
|
-
const
|
|
48
|
-
|
|
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);
|
|
64
|
+
function useIdleActorRef(logic, options) {
|
|
65
|
+
let [[currentConfig, actorRef], setCurrent] = React.useState(() => {
|
|
66
|
+
const actorRef = xstate.createActor(logic, options);
|
|
67
|
+
return [logic.config, actorRef];
|
|
54
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
|
+
}
|
|
55
79
|
|
|
56
80
|
// TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
|
|
57
81
|
useIsomorphicLayoutEffect__default["default"](() => {
|
|
58
|
-
actorRef.logic.implementations =
|
|
82
|
+
actorRef.logic.implementations = logic.implementations;
|
|
59
83
|
});
|
|
60
84
|
return actorRef;
|
|
61
85
|
}
|
|
62
|
-
function useActorRef(machine,
|
|
63
|
-
const actorRef =
|
|
86
|
+
function useActorRef(machine, options = {}, observerOrListener) {
|
|
87
|
+
const actorRef = useIdleActorRef(machine, options);
|
|
64
88
|
React.useEffect(() => {
|
|
65
89
|
if (!observerOrListener) {
|
|
66
90
|
return;
|
|
@@ -73,19 +97,17 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
|
|
|
73
97
|
React.useEffect(() => {
|
|
74
98
|
actorRef.start();
|
|
75
99
|
return () => {
|
|
76
|
-
actorRef
|
|
77
|
-
actorRef.status = xstate.ActorStatus.NotStarted;
|
|
78
|
-
actorRef._initState();
|
|
100
|
+
stopRootWithRehydration(actorRef);
|
|
79
101
|
};
|
|
80
|
-
}, []);
|
|
102
|
+
}, [actorRef]);
|
|
81
103
|
return actorRef;
|
|
82
104
|
}
|
|
83
105
|
|
|
84
106
|
function useActor(logic, options = {}) {
|
|
85
|
-
if (
|
|
107
|
+
if (!!logic && 'send' in logic && typeof logic.send === 'function') {
|
|
86
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.`);
|
|
87
109
|
}
|
|
88
|
-
const actorRef =
|
|
110
|
+
const actorRef = useIdleActorRef(logic, options);
|
|
89
111
|
const getSnapshot = React.useCallback(() => {
|
|
90
112
|
return actorRef.getSnapshot();
|
|
91
113
|
}, [actorRef]);
|
|
@@ -99,9 +121,7 @@ function useActor(logic, options = {}) {
|
|
|
99
121
|
React.useEffect(() => {
|
|
100
122
|
actorRef.start();
|
|
101
123
|
return () => {
|
|
102
|
-
actorRef
|
|
103
|
-
actorRef.status = xstate.ActorStatus.NotStarted;
|
|
104
|
-
actorRef._initState();
|
|
124
|
+
stopRootWithRehydration(actorRef);
|
|
105
125
|
};
|
|
106
126
|
}, [actorRef]);
|
|
107
127
|
return [actorSnapshot, actorRef.send, actorRef];
|
|
@@ -186,8 +206,7 @@ function createActorContext(actorLogic, interpreterOptions) {
|
|
|
186
206
|
}
|
|
187
207
|
|
|
188
208
|
/**
|
|
189
|
-
*
|
|
190
|
-
* @deprecated Use `useActor(...)` instead.
|
|
209
|
+
* @alias useActor
|
|
191
210
|
*/
|
|
192
211
|
function useMachine(machine, options = {}) {
|
|
193
212
|
return useActor(machine, options);
|
|
@@ -1,40 +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,
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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;
|
|
35
|
+
});
|
|
17
36
|
}
|
|
18
37
|
|
|
19
|
-
function
|
|
20
|
-
{
|
|
21
|
-
const
|
|
22
|
-
|
|
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);
|
|
38
|
+
function useIdleActorRef(logic, options) {
|
|
39
|
+
let [[currentConfig, actorRef], setCurrent] = useState(() => {
|
|
40
|
+
const actorRef = createActor(logic, options);
|
|
41
|
+
return [logic.config, actorRef];
|
|
28
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
|
+
}
|
|
29
53
|
|
|
30
54
|
// TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
|
|
31
55
|
useIsomorphicLayoutEffect(() => {
|
|
32
|
-
actorRef.logic.implementations =
|
|
56
|
+
actorRef.logic.implementations = logic.implementations;
|
|
33
57
|
});
|
|
34
58
|
return actorRef;
|
|
35
59
|
}
|
|
36
|
-
function useActorRef(machine,
|
|
37
|
-
const actorRef =
|
|
60
|
+
function useActorRef(machine, options = {}, observerOrListener) {
|
|
61
|
+
const actorRef = useIdleActorRef(machine, options);
|
|
38
62
|
useEffect(() => {
|
|
39
63
|
if (!observerOrListener) {
|
|
40
64
|
return;
|
|
@@ -47,19 +71,17 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
|
|
|
47
71
|
useEffect(() => {
|
|
48
72
|
actorRef.start();
|
|
49
73
|
return () => {
|
|
50
|
-
actorRef
|
|
51
|
-
actorRef.status = ActorStatus.NotStarted;
|
|
52
|
-
actorRef._initState();
|
|
74
|
+
stopRootWithRehydration(actorRef);
|
|
53
75
|
};
|
|
54
|
-
}, []);
|
|
76
|
+
}, [actorRef]);
|
|
55
77
|
return actorRef;
|
|
56
78
|
}
|
|
57
79
|
|
|
58
80
|
function useActor(logic, options = {}) {
|
|
59
|
-
if (
|
|
81
|
+
if (!!logic && 'send' in logic && typeof logic.send === 'function') {
|
|
60
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.`);
|
|
61
83
|
}
|
|
62
|
-
const actorRef =
|
|
84
|
+
const actorRef = useIdleActorRef(logic, options);
|
|
63
85
|
const getSnapshot = useCallback(() => {
|
|
64
86
|
return actorRef.getSnapshot();
|
|
65
87
|
}, [actorRef]);
|
|
@@ -73,9 +95,7 @@ function useActor(logic, options = {}) {
|
|
|
73
95
|
useEffect(() => {
|
|
74
96
|
actorRef.start();
|
|
75
97
|
return () => {
|
|
76
|
-
actorRef
|
|
77
|
-
actorRef.status = ActorStatus.NotStarted;
|
|
78
|
-
actorRef._initState();
|
|
98
|
+
stopRootWithRehydration(actorRef);
|
|
79
99
|
};
|
|
80
100
|
}, [actorRef]);
|
|
81
101
|
return [actorSnapshot, actorRef.send, actorRef];
|
|
@@ -160,8 +180,7 @@ function createActorContext(actorLogic, interpreterOptions) {
|
|
|
160
180
|
}
|
|
161
181
|
|
|
162
182
|
/**
|
|
163
|
-
*
|
|
164
|
-
* @deprecated Use `useActor(...)` instead.
|
|
183
|
+
* @alias useActor
|
|
165
184
|
*/
|
|
166
185
|
function useMachine(machine, options = {}) {
|
|
167
186
|
return useActor(machine, options);
|
package/dist/xstate-react.esm.js
CHANGED
|
@@ -1,34 +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,
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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;
|
|
35
|
+
});
|
|
17
36
|
}
|
|
18
37
|
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
function useIdleActorRef(logic, options) {
|
|
39
|
+
let [[currentConfig, actorRef], setCurrent] = useState(() => {
|
|
40
|
+
const actorRef = createActor(logic, options);
|
|
41
|
+
return [logic.config, actorRef];
|
|
22
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
|
+
}
|
|
23
53
|
|
|
24
54
|
// TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
|
|
25
55
|
useIsomorphicLayoutEffect(() => {
|
|
26
|
-
actorRef.logic.implementations =
|
|
56
|
+
actorRef.logic.implementations = logic.implementations;
|
|
27
57
|
});
|
|
28
58
|
return actorRef;
|
|
29
59
|
}
|
|
30
|
-
function useActorRef(machine,
|
|
31
|
-
const actorRef =
|
|
60
|
+
function useActorRef(machine, options = {}, observerOrListener) {
|
|
61
|
+
const actorRef = useIdleActorRef(machine, options);
|
|
32
62
|
useEffect(() => {
|
|
33
63
|
if (!observerOrListener) {
|
|
34
64
|
return;
|
|
@@ -41,16 +71,14 @@ function useActorRef(machine, ...[options = {}, observerOrListener]) {
|
|
|
41
71
|
useEffect(() => {
|
|
42
72
|
actorRef.start();
|
|
43
73
|
return () => {
|
|
44
|
-
actorRef
|
|
45
|
-
actorRef.status = ActorStatus.NotStarted;
|
|
46
|
-
actorRef._initState();
|
|
74
|
+
stopRootWithRehydration(actorRef);
|
|
47
75
|
};
|
|
48
|
-
}, []);
|
|
76
|
+
}, [actorRef]);
|
|
49
77
|
return actorRef;
|
|
50
78
|
}
|
|
51
79
|
|
|
52
80
|
function useActor(logic, options = {}) {
|
|
53
|
-
const actorRef =
|
|
81
|
+
const actorRef = useIdleActorRef(logic, options);
|
|
54
82
|
const getSnapshot = useCallback(() => {
|
|
55
83
|
return actorRef.getSnapshot();
|
|
56
84
|
}, [actorRef]);
|
|
@@ -64,9 +92,7 @@ function useActor(logic, options = {}) {
|
|
|
64
92
|
useEffect(() => {
|
|
65
93
|
actorRef.start();
|
|
66
94
|
return () => {
|
|
67
|
-
actorRef
|
|
68
|
-
actorRef.status = ActorStatus.NotStarted;
|
|
69
|
-
actorRef._initState();
|
|
95
|
+
stopRootWithRehydration(actorRef);
|
|
70
96
|
};
|
|
71
97
|
}, [actorRef]);
|
|
72
98
|
return [actorSnapshot, actorRef.send, actorRef];
|
|
@@ -151,8 +177,7 @@ function createActorContext(actorLogic, interpreterOptions) {
|
|
|
151
177
|
}
|
|
152
178
|
|
|
153
179
|
/**
|
|
154
|
-
*
|
|
155
|
-
* @deprecated Use `useActor(...)` instead.
|
|
180
|
+
* @alias useActor
|
|
156
181
|
*/
|
|
157
182
|
function useMachine(machine, options = {}) {
|
|
158
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
|
|
3
|
+
"version": "4.0.0",
|
|
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
|
|
58
|
+
"xstate": "^5.0.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
|
|
79
|
+
"xstate": "5.0.0"
|
|
80
80
|
}
|
|
81
81
|
}
|