@xstate/react 7.0.0-alpha.6 → 7.0.0-beta.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 +1 -1
- package/dist/declarations/src/createActorContext.d.ts +6 -0
- package/dist/declarations/src/shallowEqual.d.ts +1 -0
- package/dist/declarations/src/useActor.d.ts +8 -0
- package/dist/declarations/src/useActorRef.d.ts +14 -1
- package/dist/declarations/src/useMachine.d.ts +7 -1
- package/dist/declarations/src/useSelector.d.ts +1 -0
- package/dist/xstate-react.cjs.js +57 -11
- package/dist/xstate-react.development.cjs.js +59 -13
- package/dist/xstate-react.development.esm.js +60 -14
- package/dist/xstate-react.esm.js +58 -12
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { Actor, ActorOptions, AnyActorLogic, SnapshotFrom } from 'xstate';
|
|
3
|
+
/** @public */
|
|
3
4
|
export declare function createActorContext<TLogic extends AnyActorLogic>(actorLogic: TLogic, actorOptions?: ActorOptions<TLogic>): {
|
|
4
5
|
useSelector: <T>(selector: (snapshot: SnapshotFrom<TLogic>) => T, compare?: (a: T, b: T) => boolean) => T;
|
|
5
6
|
useActorRef: () => Actor<TLogic>;
|
|
@@ -7,5 +8,10 @@ export declare function createActorContext<TLogic extends AnyActorLogic>(actorLo
|
|
|
7
8
|
children: React.ReactNode;
|
|
8
9
|
options?: ActorOptions<TLogic>;
|
|
9
10
|
logic?: TLogic;
|
|
11
|
+
/**
|
|
12
|
+
* An already-created actor (e.g. from `restoreActor`) to provide instead
|
|
13
|
+
* of creating one from `logic` and `options`. Create it once.
|
|
14
|
+
*/
|
|
15
|
+
actor?: Actor<TLogic>;
|
|
10
16
|
}) => React.ReactElement<any, any>;
|
|
11
17
|
};
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { Actor, ActorOptions, AnyActorLogic, SnapshotFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys, type RequiredActorOptionsFor } from 'xstate';
|
|
2
|
+
/**
|
|
3
|
+
* Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
|
|
4
|
+
* `restoreActor`) and subscribes to its snapshot. Create a passed actor once,
|
|
5
|
+
* e.g. `useState(() => restoreActor(machine, persisted))[0]`.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export declare function useActor<TLogic extends AnyActorLogic>(actor: Actor<TLogic>): [SnapshotFrom<TLogic>, Actor<TLogic>['send'], Actor<TLogic>];
|
|
2
10
|
export declare function useActor<TLogic extends AnyActorLogic>(logic: TLogic, ...[options]: ConditionalRequired<[
|
|
3
11
|
options?: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>
|
|
4
12
|
], IsNotNever<RequiredActorOptionsKeys<TLogic>>>): [SnapshotFrom<TLogic>, Actor<TLogic>['send'], Actor<TLogic>];
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { Actor, ActorOptions, AnyActorLogic, Observer, SnapshotFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys, type RequiredActorOptionsFor } from 'xstate';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* An already-created actor (e.g. from `restoreActor`). Hooks use it as is
|
|
4
|
+
* instead of creating one from logic.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isActor(value: unknown): value is Actor<AnyActorLogic>;
|
|
7
|
+
export declare function useIdleActorRef<TLogic extends AnyActorLogic>(logicOrActor: TLogic | Actor<TLogic>, ...[options]: ConditionalRequired<[
|
|
3
8
|
options?: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>
|
|
4
9
|
], IsNotNever<RequiredActorOptionsKeys<TLogic>>>): [Actor<TLogic>, (actorRef: Actor<TLogic>) => void];
|
|
5
10
|
export declare function useActorLifecycle<TLogic extends AnyActorLogic>(actorRef: Actor<TLogic>, setActorRef: (actorRef: Actor<TLogic>) => void, createReplacement: () => Actor<TLogic>): void;
|
|
11
|
+
/**
|
|
12
|
+
* Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
|
|
13
|
+
* `restoreActor`), starts it on mount and stops it on unmount. Create a passed
|
|
14
|
+
* actor once, e.g. `useState(() => restoreActor(machine, persisted))[0]`.
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export declare function useActorRef<TLogic extends AnyActorLogic>(actor: Actor<TLogic>, observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)): Actor<TLogic>;
|
|
6
19
|
export declare function useActorRef<TLogic extends AnyActorLogic>(machine: TLogic, ...[options, observerOrListener]: IsNotNever<RequiredActorOptionsKeys<TLogic>> extends true ? [
|
|
7
20
|
options: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>,
|
|
8
21
|
observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Actor, ActorOptions, AnyStateMachine, StateFrom, type ConditionalRequired, type IsNotNever, type RequiredActorOptionsKeys, type RequiredActorOptionsFor } from 'xstate';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* @alias useActor
|
|
4
|
+
*
|
|
5
|
+
* @deprecated Use `useActor(machine, options)`.
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export declare function useMachine<TMachine extends AnyStateMachine>(actor: Actor<TMachine>): [StateFrom<TMachine>, Actor<TMachine>['send'], Actor<TMachine>];
|
|
3
9
|
export declare function useMachine<TMachine extends AnyStateMachine>(machine: TMachine, ...[options]: ConditionalRequired<[
|
|
4
10
|
options?: ActorOptions<TMachine> & RequiredActorOptionsFor<TMachine>
|
|
5
11
|
], IsNotNever<RequiredActorOptionsKeys<TMachine>>>): [StateFrom<TMachine>, Actor<TMachine>['send'], Actor<TMachine>];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AnyActorRef } from 'xstate';
|
|
2
|
+
/** @public */
|
|
2
3
|
export declare function useSelector<TActor extends Pick<AnyActorRef, 'subscribe' | 'getSnapshot'> | undefined, T>(actor: TActor, selector: (snapshot: TActor extends {
|
|
3
4
|
getSnapshot(): infer TSnapshot;
|
|
4
5
|
} ? TSnapshot : undefined) => T, compare?: (a: T, b: T) => boolean): T;
|
package/dist/xstate-react.cjs.js
CHANGED
|
@@ -35,15 +35,23 @@ function isStateMachine(logic) {
|
|
|
35
35
|
return typeof logic.provide === 'function' && !!logic.config && !!logic.sources;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* An already-created actor (e.g. from `restoreActor`). Hooks use it as is
|
|
40
|
+
* instead of creating one from logic.
|
|
41
|
+
*/
|
|
42
|
+
function isActor(value) {
|
|
43
|
+
return value instanceof xstate.Actor;
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
// The hook updates the sources of its machine on later renders. A per-hook
|
|
39
47
|
// copy keeps those updates from reaching the caller's machine, which may be
|
|
40
48
|
// shared by other actors.
|
|
41
49
|
function ownLogic(logic) {
|
|
42
50
|
return isStateMachine(logic) ? logic.provide({}) : logic;
|
|
43
51
|
}
|
|
44
|
-
function useIdleActorRef(
|
|
52
|
+
function useIdleActorRef(logicOrActor, ...[options]) {
|
|
45
53
|
const [actorRef, setActorRef] = React.useState(() => {
|
|
46
|
-
return xstate.createActor(ownLogic(
|
|
54
|
+
return isActor(logicOrActor) ? logicOrActor : xstate.createActor(ownLogic(logicOrActor), options);
|
|
47
55
|
});
|
|
48
56
|
// An object whose identity changes only when React Fast Refresh re-renders
|
|
49
57
|
// this component. Fast Refresh ignores dependency lists while it applies an
|
|
@@ -57,6 +65,11 @@ function useIdleActorRef(logic, ...[options]) {
|
|
|
57
65
|
// `machine.provide()` for the same machine config.
|
|
58
66
|
// TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
|
|
59
67
|
useIsomorphicLayoutEffect__default["default"](() => {
|
|
68
|
+
// A ready actor is owned as is; its logic is not updated by later renders.
|
|
69
|
+
if (isActor(logicOrActor)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const logic = logicOrActor;
|
|
60
73
|
const currentLogic = actorRef.logic;
|
|
61
74
|
refreshSignalRef.current !== refreshSignal;
|
|
62
75
|
refreshSignalRef.current = refreshSignal;
|
|
@@ -103,8 +116,20 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
|
|
|
103
116
|
};
|
|
104
117
|
}, [actorRef]);
|
|
105
118
|
}
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
|
|
122
|
+
* `restoreActor`), starts it on mount and stops it on unmount. Create a passed
|
|
123
|
+
* actor once, e.g. `useState(() => restoreActor(machine, persisted))[0]`.
|
|
124
|
+
*
|
|
125
|
+
* @public
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
function useActorRef(logicOrActor, ...args) {
|
|
129
|
+
const readyActor = isActor(logicOrActor);
|
|
130
|
+
const options = readyActor ? undefined : args[0];
|
|
131
|
+
const observerOrListener = readyActor ? args[0] : args[1];
|
|
132
|
+
const [actorRef, setActorRef] = useIdleActorRef(logicOrActor, options);
|
|
108
133
|
React.useEffect(() => {
|
|
109
134
|
if (!observerOrListener) {
|
|
110
135
|
return;
|
|
@@ -121,6 +146,8 @@ function useActorRef(machine, ...[options, observerOrListener]) {
|
|
|
121
146
|
function defaultCompare(a, b) {
|
|
122
147
|
return a === b;
|
|
123
148
|
}
|
|
149
|
+
|
|
150
|
+
/** @public */
|
|
124
151
|
function useSelector(actor, selector, compare = defaultCompare) {
|
|
125
152
|
const subscribe = React.useCallback(handleStoreChange => {
|
|
126
153
|
if (!actor) {
|
|
@@ -145,15 +172,17 @@ function useSelector(actor, selector, compare = defaultCompare) {
|
|
|
145
172
|
return selectedSnapshot;
|
|
146
173
|
}
|
|
147
174
|
|
|
175
|
+
/** @public */
|
|
148
176
|
function createActorContext(actorLogic, actorOptions) {
|
|
149
177
|
const ReactContext = /*#__PURE__*/React__namespace.createContext(null);
|
|
150
178
|
const OriginalProvider = ReactContext.Provider;
|
|
151
179
|
function Provider({
|
|
152
180
|
children,
|
|
153
181
|
logic: providedLogic = actorLogic,
|
|
154
|
-
options: providedOptions
|
|
182
|
+
options: providedOptions,
|
|
183
|
+
actor: providedActor
|
|
155
184
|
}) {
|
|
156
|
-
const actor = useActorRef(providedLogic, {
|
|
185
|
+
const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
|
|
157
186
|
...actorOptions,
|
|
158
187
|
...providedOptions
|
|
159
188
|
});
|
|
@@ -191,6 +220,8 @@ function is(x, y) {
|
|
|
191
220
|
return x !== x && y !== y;
|
|
192
221
|
}
|
|
193
222
|
}
|
|
223
|
+
|
|
224
|
+
/** @public */
|
|
194
225
|
function shallowEqual(objA, objB) {
|
|
195
226
|
if (is(objA, objB)) return true;
|
|
196
227
|
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
|
|
@@ -207,8 +238,17 @@ function shallowEqual(objA, objB) {
|
|
|
207
238
|
return true;
|
|
208
239
|
}
|
|
209
240
|
|
|
210
|
-
|
|
211
|
-
|
|
241
|
+
/**
|
|
242
|
+
* Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
|
|
243
|
+
* `restoreActor`) and subscribes to its snapshot. Create a passed actor once,
|
|
244
|
+
* e.g. `useState(() => restoreActor(machine, persisted))[0]`.
|
|
245
|
+
*
|
|
246
|
+
* @public
|
|
247
|
+
*/
|
|
248
|
+
|
|
249
|
+
function useActor(logic, options) {
|
|
250
|
+
const readyActor = isActor(logic);
|
|
251
|
+
const [actorRef, setActorRef] = useIdleActorRef(logic, ...[options]);
|
|
212
252
|
const getSnapshot = React.useCallback(() => {
|
|
213
253
|
return actorRef.getSnapshot();
|
|
214
254
|
}, [actorRef]);
|
|
@@ -226,12 +266,18 @@ function useActor(logic, ...[options]) {
|
|
|
226
266
|
if (snapshotWithStatus?.status === 'error') {
|
|
227
267
|
throw snapshotWithStatus.error;
|
|
228
268
|
}
|
|
229
|
-
useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, options));
|
|
269
|
+
useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, readyActor ? actorRef.options : options));
|
|
230
270
|
return [actorSnapshot, actorRef.send, actorRef];
|
|
231
271
|
}
|
|
232
272
|
|
|
233
|
-
/**
|
|
234
|
-
|
|
273
|
+
/**
|
|
274
|
+
* @alias useActor
|
|
275
|
+
*
|
|
276
|
+
* @deprecated Use `useActor(machine, options)`.
|
|
277
|
+
* @public
|
|
278
|
+
*/
|
|
279
|
+
|
|
280
|
+
function useMachine(machine, options) {
|
|
235
281
|
return useActor(machine, options);
|
|
236
282
|
}
|
|
237
283
|
|
|
@@ -35,15 +35,23 @@ function isStateMachine(logic) {
|
|
|
35
35
|
return typeof logic.provide === 'function' && !!logic.config && !!logic.sources;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* An already-created actor (e.g. from `restoreActor`). Hooks use it as is
|
|
40
|
+
* instead of creating one from logic.
|
|
41
|
+
*/
|
|
42
|
+
function isActor(value) {
|
|
43
|
+
return value instanceof xstate.Actor;
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
// The hook updates the sources of its machine on later renders. A per-hook
|
|
39
47
|
// copy keeps those updates from reaching the caller's machine, which may be
|
|
40
48
|
// shared by other actors.
|
|
41
49
|
function ownLogic(logic) {
|
|
42
50
|
return isStateMachine(logic) ? logic.provide({}) : logic;
|
|
43
51
|
}
|
|
44
|
-
function useIdleActorRef(
|
|
52
|
+
function useIdleActorRef(logicOrActor, ...[options]) {
|
|
45
53
|
const [actorRef, setActorRef] = React.useState(() => {
|
|
46
|
-
return xstate.createActor(ownLogic(
|
|
54
|
+
return isActor(logicOrActor) ? logicOrActor : xstate.createActor(ownLogic(logicOrActor), options);
|
|
47
55
|
});
|
|
48
56
|
// An object whose identity changes only when React Fast Refresh re-renders
|
|
49
57
|
// this component. Fast Refresh ignores dependency lists while it applies an
|
|
@@ -57,6 +65,11 @@ function useIdleActorRef(logic, ...[options]) {
|
|
|
57
65
|
// `machine.provide()` for the same machine config.
|
|
58
66
|
// TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
|
|
59
67
|
useIsomorphicLayoutEffect__default["default"](() => {
|
|
68
|
+
// A ready actor is owned as is; its logic is not updated by later renders.
|
|
69
|
+
if (isActor(logicOrActor)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const logic = logicOrActor;
|
|
60
73
|
const currentLogic = actorRef.logic;
|
|
61
74
|
const refreshed = refreshSignalRef.current !== refreshSignal;
|
|
62
75
|
refreshSignalRef.current = refreshSignal;
|
|
@@ -113,8 +126,20 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
|
|
|
113
126
|
};
|
|
114
127
|
}, [actorRef]);
|
|
115
128
|
}
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
|
|
132
|
+
* `restoreActor`), starts it on mount and stops it on unmount. Create a passed
|
|
133
|
+
* actor once, e.g. `useState(() => restoreActor(machine, persisted))[0]`.
|
|
134
|
+
*
|
|
135
|
+
* @public
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
function useActorRef(logicOrActor, ...args) {
|
|
139
|
+
const readyActor = isActor(logicOrActor);
|
|
140
|
+
const options = readyActor ? undefined : args[0];
|
|
141
|
+
const observerOrListener = readyActor ? args[0] : args[1];
|
|
142
|
+
const [actorRef, setActorRef] = useIdleActorRef(logicOrActor, options);
|
|
118
143
|
React.useEffect(() => {
|
|
119
144
|
if (!observerOrListener) {
|
|
120
145
|
return;
|
|
@@ -131,6 +156,8 @@ function useActorRef(machine, ...[options, observerOrListener]) {
|
|
|
131
156
|
function defaultCompare(a, b) {
|
|
132
157
|
return a === b;
|
|
133
158
|
}
|
|
159
|
+
|
|
160
|
+
/** @public */
|
|
134
161
|
function useSelector(actor, selector, compare = defaultCompare) {
|
|
135
162
|
const subscribe = React.useCallback(handleStoreChange => {
|
|
136
163
|
if (!actor) {
|
|
@@ -155,15 +182,17 @@ function useSelector(actor, selector, compare = defaultCompare) {
|
|
|
155
182
|
return selectedSnapshot;
|
|
156
183
|
}
|
|
157
184
|
|
|
185
|
+
/** @public */
|
|
158
186
|
function createActorContext(actorLogic, actorOptions) {
|
|
159
187
|
const ReactContext = /*#__PURE__*/React__namespace.createContext(null);
|
|
160
188
|
const OriginalProvider = ReactContext.Provider;
|
|
161
189
|
function Provider({
|
|
162
190
|
children,
|
|
163
191
|
logic: providedLogic = actorLogic,
|
|
164
|
-
options: providedOptions
|
|
192
|
+
options: providedOptions,
|
|
193
|
+
actor: providedActor
|
|
165
194
|
}) {
|
|
166
|
-
const actor = useActorRef(providedLogic, {
|
|
195
|
+
const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
|
|
167
196
|
...actorOptions,
|
|
168
197
|
...providedOptions
|
|
169
198
|
});
|
|
@@ -201,6 +230,8 @@ function is(x, y) {
|
|
|
201
230
|
return x !== x && y !== y;
|
|
202
231
|
}
|
|
203
232
|
}
|
|
233
|
+
|
|
234
|
+
/** @public */
|
|
204
235
|
function shallowEqual(objA, objB) {
|
|
205
236
|
if (is(objA, objB)) return true;
|
|
206
237
|
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
|
|
@@ -217,11 +248,20 @@ function shallowEqual(objA, objB) {
|
|
|
217
248
|
return true;
|
|
218
249
|
}
|
|
219
250
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
|
|
253
|
+
* `restoreActor`) and subscribes to its snapshot. Create a passed actor once,
|
|
254
|
+
* e.g. `useState(() => restoreActor(machine, persisted))[0]`.
|
|
255
|
+
*
|
|
256
|
+
* @public
|
|
257
|
+
*/
|
|
258
|
+
|
|
259
|
+
function useActor(logic, options) {
|
|
260
|
+
const readyActor = isActor(logic);
|
|
261
|
+
if (!readyActor && !!logic && 'send' in logic && typeof logic.send === 'function') {
|
|
262
|
+
throw new Error(`useActor() expects actor logic (e.g. a machine), but received an ActorRef. Pass an Actor created with createActor()/restoreActor(), or use the useSelector(actorRef, ...) hook to read an ActorRef's snapshot.`);
|
|
223
263
|
}
|
|
224
|
-
const [actorRef, setActorRef] = useIdleActorRef(logic, options);
|
|
264
|
+
const [actorRef, setActorRef] = useIdleActorRef(logic, ...[options]);
|
|
225
265
|
const getSnapshot = React.useCallback(() => {
|
|
226
266
|
return actorRef.getSnapshot();
|
|
227
267
|
}, [actorRef]);
|
|
@@ -239,12 +279,18 @@ function useActor(logic, ...[options]) {
|
|
|
239
279
|
if (snapshotWithStatus?.status === 'error') {
|
|
240
280
|
throw snapshotWithStatus.error;
|
|
241
281
|
}
|
|
242
|
-
useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, options));
|
|
282
|
+
useActorLifecycle(actorRef, setActorRef, () => xstate.createActor(actorRef.logic, readyActor ? actorRef.options : options));
|
|
243
283
|
return [actorSnapshot, actorRef.send, actorRef];
|
|
244
284
|
}
|
|
245
285
|
|
|
246
|
-
/**
|
|
247
|
-
|
|
286
|
+
/**
|
|
287
|
+
* @alias useActor
|
|
288
|
+
*
|
|
289
|
+
* @deprecated Use `useActor(machine, options)`.
|
|
290
|
+
* @public
|
|
291
|
+
*/
|
|
292
|
+
|
|
293
|
+
function useMachine(machine, options) {
|
|
248
294
|
return useActor(machine, options);
|
|
249
295
|
}
|
|
250
296
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useEffect, useState, useMemo, useRef, useCallback } from 'react';
|
|
3
3
|
import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
|
|
4
|
-
import { _toObserver, createActor, _hotSwapActorLogic } from 'xstate';
|
|
4
|
+
import { _toObserver, createActor, _hotSwapActorLogic, Actor } from 'xstate';
|
|
5
5
|
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
|
|
6
6
|
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
7
7
|
|
|
@@ -9,15 +9,23 @@ function isStateMachine(logic) {
|
|
|
9
9
|
return typeof logic.provide === 'function' && !!logic.config && !!logic.sources;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* An already-created actor (e.g. from `restoreActor`). Hooks use it as is
|
|
14
|
+
* instead of creating one from logic.
|
|
15
|
+
*/
|
|
16
|
+
function isActor(value) {
|
|
17
|
+
return value instanceof Actor;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
// The hook updates the sources of its machine on later renders. A per-hook
|
|
13
21
|
// copy keeps those updates from reaching the caller's machine, which may be
|
|
14
22
|
// shared by other actors.
|
|
15
23
|
function ownLogic(logic) {
|
|
16
24
|
return isStateMachine(logic) ? logic.provide({}) : logic;
|
|
17
25
|
}
|
|
18
|
-
function useIdleActorRef(
|
|
26
|
+
function useIdleActorRef(logicOrActor, ...[options]) {
|
|
19
27
|
const [actorRef, setActorRef] = useState(() => {
|
|
20
|
-
return createActor(ownLogic(
|
|
28
|
+
return isActor(logicOrActor) ? logicOrActor : createActor(ownLogic(logicOrActor), options);
|
|
21
29
|
});
|
|
22
30
|
// An object whose identity changes only when React Fast Refresh re-renders
|
|
23
31
|
// this component. Fast Refresh ignores dependency lists while it applies an
|
|
@@ -31,6 +39,11 @@ function useIdleActorRef(logic, ...[options]) {
|
|
|
31
39
|
// `machine.provide()` for the same machine config.
|
|
32
40
|
// TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
|
|
33
41
|
useIsomorphicLayoutEffect(() => {
|
|
42
|
+
// A ready actor is owned as is; its logic is not updated by later renders.
|
|
43
|
+
if (isActor(logicOrActor)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const logic = logicOrActor;
|
|
34
47
|
const currentLogic = actorRef.logic;
|
|
35
48
|
const refreshed = refreshSignalRef.current !== refreshSignal;
|
|
36
49
|
refreshSignalRef.current = refreshSignal;
|
|
@@ -87,8 +100,20 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
|
|
|
87
100
|
};
|
|
88
101
|
}, [actorRef]);
|
|
89
102
|
}
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
|
|
106
|
+
* `restoreActor`), starts it on mount and stops it on unmount. Create a passed
|
|
107
|
+
* actor once, e.g. `useState(() => restoreActor(machine, persisted))[0]`.
|
|
108
|
+
*
|
|
109
|
+
* @public
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
function useActorRef(logicOrActor, ...args) {
|
|
113
|
+
const readyActor = isActor(logicOrActor);
|
|
114
|
+
const options = readyActor ? undefined : args[0];
|
|
115
|
+
const observerOrListener = readyActor ? args[0] : args[1];
|
|
116
|
+
const [actorRef, setActorRef] = useIdleActorRef(logicOrActor, options);
|
|
92
117
|
useEffect(() => {
|
|
93
118
|
if (!observerOrListener) {
|
|
94
119
|
return;
|
|
@@ -105,6 +130,8 @@ function useActorRef(machine, ...[options, observerOrListener]) {
|
|
|
105
130
|
function defaultCompare(a, b) {
|
|
106
131
|
return a === b;
|
|
107
132
|
}
|
|
133
|
+
|
|
134
|
+
/** @public */
|
|
108
135
|
function useSelector(actor, selector, compare = defaultCompare) {
|
|
109
136
|
const subscribe = useCallback(handleStoreChange => {
|
|
110
137
|
if (!actor) {
|
|
@@ -129,15 +156,17 @@ function useSelector(actor, selector, compare = defaultCompare) {
|
|
|
129
156
|
return selectedSnapshot;
|
|
130
157
|
}
|
|
131
158
|
|
|
159
|
+
/** @public */
|
|
132
160
|
function createActorContext(actorLogic, actorOptions) {
|
|
133
161
|
const ReactContext = /*#__PURE__*/React.createContext(null);
|
|
134
162
|
const OriginalProvider = ReactContext.Provider;
|
|
135
163
|
function Provider({
|
|
136
164
|
children,
|
|
137
165
|
logic: providedLogic = actorLogic,
|
|
138
|
-
options: providedOptions
|
|
166
|
+
options: providedOptions,
|
|
167
|
+
actor: providedActor
|
|
139
168
|
}) {
|
|
140
|
-
const actor = useActorRef(providedLogic, {
|
|
169
|
+
const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
|
|
141
170
|
...actorOptions,
|
|
142
171
|
...providedOptions
|
|
143
172
|
});
|
|
@@ -175,6 +204,8 @@ function is(x, y) {
|
|
|
175
204
|
return x !== x && y !== y;
|
|
176
205
|
}
|
|
177
206
|
}
|
|
207
|
+
|
|
208
|
+
/** @public */
|
|
178
209
|
function shallowEqual(objA, objB) {
|
|
179
210
|
if (is(objA, objB)) return true;
|
|
180
211
|
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
|
|
@@ -191,11 +222,20 @@ function shallowEqual(objA, objB) {
|
|
|
191
222
|
return true;
|
|
192
223
|
}
|
|
193
224
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
|
|
227
|
+
* `restoreActor`) and subscribes to its snapshot. Create a passed actor once,
|
|
228
|
+
* e.g. `useState(() => restoreActor(machine, persisted))[0]`.
|
|
229
|
+
*
|
|
230
|
+
* @public
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
function useActor(logic, options) {
|
|
234
|
+
const readyActor = isActor(logic);
|
|
235
|
+
if (!readyActor && !!logic && 'send' in logic && typeof logic.send === 'function') {
|
|
236
|
+
throw new Error(`useActor() expects actor logic (e.g. a machine), but received an ActorRef. Pass an Actor created with createActor()/restoreActor(), or use the useSelector(actorRef, ...) hook to read an ActorRef's snapshot.`);
|
|
197
237
|
}
|
|
198
|
-
const [actorRef, setActorRef] = useIdleActorRef(logic, options);
|
|
238
|
+
const [actorRef, setActorRef] = useIdleActorRef(logic, ...[options]);
|
|
199
239
|
const getSnapshot = useCallback(() => {
|
|
200
240
|
return actorRef.getSnapshot();
|
|
201
241
|
}, [actorRef]);
|
|
@@ -213,12 +253,18 @@ function useActor(logic, ...[options]) {
|
|
|
213
253
|
if (snapshotWithStatus?.status === 'error') {
|
|
214
254
|
throw snapshotWithStatus.error;
|
|
215
255
|
}
|
|
216
|
-
useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
|
|
256
|
+
useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, readyActor ? actorRef.options : options));
|
|
217
257
|
return [actorSnapshot, actorRef.send, actorRef];
|
|
218
258
|
}
|
|
219
259
|
|
|
220
|
-
/**
|
|
221
|
-
|
|
260
|
+
/**
|
|
261
|
+
* @alias useActor
|
|
262
|
+
*
|
|
263
|
+
* @deprecated Use `useActor(machine, options)`.
|
|
264
|
+
* @public
|
|
265
|
+
*/
|
|
266
|
+
|
|
267
|
+
function useMachine(machine, options) {
|
|
222
268
|
return useActor(machine, options);
|
|
223
269
|
}
|
|
224
270
|
|
package/dist/xstate-react.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useEffect, useState, useMemo, useRef, useCallback } from 'react';
|
|
3
3
|
import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect';
|
|
4
|
-
import { _toObserver, createActor } from 'xstate';
|
|
4
|
+
import { _toObserver, createActor, Actor } from 'xstate';
|
|
5
5
|
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
|
|
6
6
|
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
7
7
|
|
|
@@ -9,15 +9,23 @@ function isStateMachine(logic) {
|
|
|
9
9
|
return typeof logic.provide === 'function' && !!logic.config && !!logic.sources;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* An already-created actor (e.g. from `restoreActor`). Hooks use it as is
|
|
14
|
+
* instead of creating one from logic.
|
|
15
|
+
*/
|
|
16
|
+
function isActor(value) {
|
|
17
|
+
return value instanceof Actor;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
// The hook updates the sources of its machine on later renders. A per-hook
|
|
13
21
|
// copy keeps those updates from reaching the caller's machine, which may be
|
|
14
22
|
// shared by other actors.
|
|
15
23
|
function ownLogic(logic) {
|
|
16
24
|
return isStateMachine(logic) ? logic.provide({}) : logic;
|
|
17
25
|
}
|
|
18
|
-
function useIdleActorRef(
|
|
26
|
+
function useIdleActorRef(logicOrActor, ...[options]) {
|
|
19
27
|
const [actorRef, setActorRef] = useState(() => {
|
|
20
|
-
return createActor(ownLogic(
|
|
28
|
+
return isActor(logicOrActor) ? logicOrActor : createActor(ownLogic(logicOrActor), options);
|
|
21
29
|
});
|
|
22
30
|
// An object whose identity changes only when React Fast Refresh re-renders
|
|
23
31
|
// this component. Fast Refresh ignores dependency lists while it applies an
|
|
@@ -31,6 +39,11 @@ function useIdleActorRef(logic, ...[options]) {
|
|
|
31
39
|
// `machine.provide()` for the same machine config.
|
|
32
40
|
// TODO: consider using `useAsapEffect` that would do this in `useInsertionEffect` is that's available
|
|
33
41
|
useIsomorphicLayoutEffect(() => {
|
|
42
|
+
// A ready actor is owned as is; its logic is not updated by later renders.
|
|
43
|
+
if (isActor(logicOrActor)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const logic = logicOrActor;
|
|
34
47
|
const currentLogic = actorRef.logic;
|
|
35
48
|
refreshSignalRef.current !== refreshSignal;
|
|
36
49
|
refreshSignalRef.current = refreshSignal;
|
|
@@ -77,8 +90,20 @@ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
|
|
|
77
90
|
};
|
|
78
91
|
}, [actorRef]);
|
|
79
92
|
}
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
|
|
96
|
+
* `restoreActor`), starts it on mount and stops it on unmount. Create a passed
|
|
97
|
+
* actor once, e.g. `useState(() => restoreActor(machine, persisted))[0]`.
|
|
98
|
+
*
|
|
99
|
+
* @public
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
function useActorRef(logicOrActor, ...args) {
|
|
103
|
+
const readyActor = isActor(logicOrActor);
|
|
104
|
+
const options = readyActor ? undefined : args[0];
|
|
105
|
+
const observerOrListener = readyActor ? args[0] : args[1];
|
|
106
|
+
const [actorRef, setActorRef] = useIdleActorRef(logicOrActor, options);
|
|
82
107
|
useEffect(() => {
|
|
83
108
|
if (!observerOrListener) {
|
|
84
109
|
return;
|
|
@@ -95,6 +120,8 @@ function useActorRef(machine, ...[options, observerOrListener]) {
|
|
|
95
120
|
function defaultCompare(a, b) {
|
|
96
121
|
return a === b;
|
|
97
122
|
}
|
|
123
|
+
|
|
124
|
+
/** @public */
|
|
98
125
|
function useSelector(actor, selector, compare = defaultCompare) {
|
|
99
126
|
const subscribe = useCallback(handleStoreChange => {
|
|
100
127
|
if (!actor) {
|
|
@@ -119,15 +146,17 @@ function useSelector(actor, selector, compare = defaultCompare) {
|
|
|
119
146
|
return selectedSnapshot;
|
|
120
147
|
}
|
|
121
148
|
|
|
149
|
+
/** @public */
|
|
122
150
|
function createActorContext(actorLogic, actorOptions) {
|
|
123
151
|
const ReactContext = /*#__PURE__*/React.createContext(null);
|
|
124
152
|
const OriginalProvider = ReactContext.Provider;
|
|
125
153
|
function Provider({
|
|
126
154
|
children,
|
|
127
155
|
logic: providedLogic = actorLogic,
|
|
128
|
-
options: providedOptions
|
|
156
|
+
options: providedOptions,
|
|
157
|
+
actor: providedActor
|
|
129
158
|
}) {
|
|
130
|
-
const actor = useActorRef(providedLogic, {
|
|
159
|
+
const actor = useActorRef(providedActor ?? providedLogic, providedActor ? undefined : {
|
|
131
160
|
...actorOptions,
|
|
132
161
|
...providedOptions
|
|
133
162
|
});
|
|
@@ -165,6 +194,8 @@ function is(x, y) {
|
|
|
165
194
|
return x !== x && y !== y;
|
|
166
195
|
}
|
|
167
196
|
}
|
|
197
|
+
|
|
198
|
+
/** @public */
|
|
168
199
|
function shallowEqual(objA, objB) {
|
|
169
200
|
if (is(objA, objB)) return true;
|
|
170
201
|
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
|
|
@@ -181,8 +212,17 @@ function shallowEqual(objA, objB) {
|
|
|
181
212
|
return true;
|
|
182
213
|
}
|
|
183
214
|
|
|
184
|
-
|
|
185
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Creates an actor from `logic` (or uses an already-created `actor`, e.g. from
|
|
217
|
+
* `restoreActor`) and subscribes to its snapshot. Create a passed actor once,
|
|
218
|
+
* e.g. `useState(() => restoreActor(machine, persisted))[0]`.
|
|
219
|
+
*
|
|
220
|
+
* @public
|
|
221
|
+
*/
|
|
222
|
+
|
|
223
|
+
function useActor(logic, options) {
|
|
224
|
+
const readyActor = isActor(logic);
|
|
225
|
+
const [actorRef, setActorRef] = useIdleActorRef(logic, ...[options]);
|
|
186
226
|
const getSnapshot = useCallback(() => {
|
|
187
227
|
return actorRef.getSnapshot();
|
|
188
228
|
}, [actorRef]);
|
|
@@ -200,12 +240,18 @@ function useActor(logic, ...[options]) {
|
|
|
200
240
|
if (snapshotWithStatus?.status === 'error') {
|
|
201
241
|
throw snapshotWithStatus.error;
|
|
202
242
|
}
|
|
203
|
-
useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
|
|
243
|
+
useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, readyActor ? actorRef.options : options));
|
|
204
244
|
return [actorSnapshot, actorRef.send, actorRef];
|
|
205
245
|
}
|
|
206
246
|
|
|
207
|
-
/**
|
|
208
|
-
|
|
247
|
+
/**
|
|
248
|
+
* @alias useActor
|
|
249
|
+
*
|
|
250
|
+
* @deprecated Use `useActor(machine, options)`.
|
|
251
|
+
* @public
|
|
252
|
+
*/
|
|
253
|
+
|
|
254
|
+
function useMachine(machine, options) {
|
|
209
255
|
return useActor(machine, options);
|
|
210
256
|
}
|
|
211
257
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xstate/react",
|
|
3
|
-
"version": "7.0.0-
|
|
3
|
+
"version": "7.0.0-beta.0",
|
|
4
4
|
"description": "XState tools for React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"state",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
57
|
-
"xstate": "^6.0.0-
|
|
57
|
+
"xstate": "^6.0.0-beta.0"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"use-isomorphic-layout-effect": "^1.1.2",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"react-dom": "^19.0.0",
|
|
70
70
|
"rxjs": "^7.8.1",
|
|
71
71
|
"zod": "^3.25.51",
|
|
72
|
-
"xstate": "^6.0.0-
|
|
72
|
+
"xstate": "^6.0.0-beta.0"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {}
|
|
75
75
|
}
|