@xstate/react 2.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,5 @@
1
- import { ActionFunction, AreAllImplementationsAssumedToBeProvided, EventObject, InternalMachineOptions, InterpreterFrom, InterpreterOptions, StateConfig, StateFrom, StateMachine } from 'xstate';
2
- import { MaybeLazy, Prop, ReactActionFunction } from './types';
3
- export declare function asEffect<TContext, TEvent extends EventObject>(exec: ActionFunction<TContext, TEvent>): ReactActionFunction<TContext, TEvent>;
4
- export declare function asLayoutEffect<TContext, TEvent extends EventObject>(exec: ActionFunction<TContext, TEvent>): ReactActionFunction<TContext, TEvent>;
1
+ import { AnyStateMachine, AreAllImplementationsAssumedToBeProvided, EventObject, InternalMachineOptions, InterpreterFrom, InterpreterOptions, StateConfig, StateFrom } from 'xstate';
2
+ import { MaybeLazy, Prop } from './types';
5
3
  export interface UseMachineOptions<TContext, TEvent extends EventObject> {
6
4
  /**
7
5
  * If provided, will be merged with machine's `context`.
@@ -13,12 +11,12 @@ export interface UseMachineOptions<TContext, TEvent extends EventObject> {
13
11
  */
14
12
  state?: StateConfig<TContext, TEvent>;
15
13
  }
16
- declare type RestParams<TMachine extends StateMachine<any, any, any, any, any, any, any>> = AreAllImplementationsAssumedToBeProvided<TMachine['__TResolvedTypesMeta']> extends false ? [
14
+ declare type RestParams<TMachine extends AnyStateMachine> = AreAllImplementationsAssumedToBeProvided<TMachine['__TResolvedTypesMeta']> extends false ? [
17
15
  options: InterpreterOptions & UseMachineOptions<TMachine['__TContext'], TMachine['__TEvent']> & InternalMachineOptions<TMachine['__TContext'], TMachine['__TEvent'], TMachine['__TResolvedTypesMeta'], true>
18
16
  ] : [
19
17
  options?: InterpreterOptions & UseMachineOptions<TMachine['__TContext'], TMachine['__TEvent']> & InternalMachineOptions<TMachine['__TContext'], TMachine['__TEvent'], TMachine['__TResolvedTypesMeta']>
20
18
  ];
21
- declare type UseMachineReturn<TMachine extends StateMachine<any, any, any, any, any, any, any>, TInterpreter = InterpreterFrom<TMachine>> = [StateFrom<TMachine>, Prop<TInterpreter, 'send'>, TInterpreter];
22
- export declare function useMachine<TMachine extends StateMachine<any, any, any, any, any, any, any>>(getMachine: MaybeLazy<TMachine>, ...[options]: RestParams<TMachine>): UseMachineReturn<TMachine>;
19
+ declare type UseMachineReturn<TMachine extends AnyStateMachine, TInterpreter = InterpreterFrom<TMachine>> = [StateFrom<TMachine>, Prop<TInterpreter, 'send'>, TInterpreter];
20
+ export declare function useMachine<TMachine extends AnyStateMachine>(getMachine: MaybeLazy<TMachine>, ...[options]: RestParams<TMachine>): UseMachineReturn<TMachine>;
23
21
  export {};
24
22
  //# sourceMappingURL=useMachine.d.ts.map
package/lib/useMachine.js CHANGED
@@ -15,70 +15,59 @@ var __read = (this && this.__read) || function (o, n) {
15
15
  }
16
16
  return ar;
17
17
  };
18
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
19
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
20
- if (ar || !(i in from)) {
21
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
22
- ar[i] = from[i];
23
- }
24
- }
25
- return to.concat(ar || Array.prototype.slice.call(from));
26
- };
27
18
  Object.defineProperty(exports, "__esModule", { value: true });
28
- exports.useMachine = exports.asLayoutEffect = exports.asEffect = void 0;
19
+ exports.useMachine = void 0;
29
20
  var react_1 = require("react");
21
+ var with_selector_1 = require("use-sync-external-store/shim/with-selector");
30
22
  var xstate_1 = require("xstate");
31
- var types_1 = require("./types");
32
23
  var useInterpret_1 = require("./useInterpret");
33
- function createReactActionFunction(exec, tag) {
34
- var effectExec = function () {
35
- var args = [];
36
- for (var _i = 0; _i < arguments.length; _i++) {
37
- args[_i] = arguments[_i];
38
- }
39
- // don't execute; just return
40
- return function () {
41
- return exec.apply(void 0, __spreadArray([], __read(args), false));
42
- };
43
- };
44
- Object.defineProperties(effectExec, {
45
- name: { value: "effect:".concat(exec.name) },
46
- __effect: { value: tag }
47
- });
48
- return effectExec;
49
- }
50
- function asEffect(exec) {
51
- return createReactActionFunction(exec, types_1.ReactEffectType.Effect);
24
+ function identity(a) {
25
+ return a;
52
26
  }
53
- exports.asEffect = asEffect;
54
- function asLayoutEffect(exec) {
55
- return createReactActionFunction(exec, types_1.ReactEffectType.LayoutEffect);
56
- }
57
- exports.asLayoutEffect = asLayoutEffect;
58
27
  function useMachine(getMachine) {
59
28
  var _a = [];
60
29
  for (var _i = 1; _i < arguments.length; _i++) {
61
30
  _a[_i - 1] = arguments[_i];
62
31
  }
63
32
  var _b = __read(_a, 1), _c = _b[0], options = _c === void 0 ? {} : _c;
64
- var listener = (0, react_1.useCallback)(function (nextState) {
33
+ // using `useIdleInterpreter` allows us to subscribe to the service *before* we start it
34
+ // so we don't miss any notifications
35
+ var service = (0, useInterpret_1.useIdleInterpreter)(getMachine, options);
36
+ var getSnapshot = (0, react_1.useCallback)(function () {
37
+ if (service.status === xstate_1.InterpreterStatus.NotStarted) {
38
+ return (options.state
39
+ ? xstate_1.State.create(options.state)
40
+ : service.machine.initialState);
41
+ }
42
+ return service.state;
43
+ }, [service]);
44
+ var isEqual = (0, react_1.useCallback)(function (prevState, nextState) {
45
+ if (service.status === xstate_1.InterpreterStatus.NotStarted) {
46
+ return true;
47
+ }
65
48
  // Only change the current state if:
66
49
  // - the incoming state is the "live" initial state (since it might have new actors)
67
50
  // - OR the incoming state actually changed.
68
51
  //
69
52
  // The "live" initial state will have .changed === undefined.
70
- var initialStateChanged = nextState.changed === undefined && Object.keys(nextState.children).length;
71
- if (nextState.changed || initialStateChanged) {
72
- setState(nextState);
73
- }
53
+ var initialStateChanged = nextState.changed === undefined &&
54
+ (Object.keys(nextState.children).length > 0 ||
55
+ typeof prevState.changed === 'boolean');
56
+ return !(nextState.changed || initialStateChanged);
57
+ }, [service]);
58
+ var subscribe = (0, react_1.useCallback)(function (handleStoreChange) {
59
+ var unsubscribe = service.subscribe(handleStoreChange).unsubscribe;
60
+ return unsubscribe;
61
+ }, [service]);
62
+ var storeSnapshot = (0, with_selector_1.useSyncExternalStoreWithSelector)(subscribe, getSnapshot, getSnapshot, identity, isEqual);
63
+ (0, react_1.useEffect)(function () {
64
+ var rehydratedState = options.state;
65
+ service.start(rehydratedState ? xstate_1.State.create(rehydratedState) : undefined);
66
+ return function () {
67
+ service.stop();
68
+ service.status = xstate_1.InterpreterStatus.NotStarted;
69
+ };
74
70
  }, []);
75
- var service = (0, useInterpret_1.useInterpret)(getMachine, options, listener);
76
- var _d = __read((0, react_1.useState)(function () {
77
- var initialState = service.machine.initialState;
78
- return (options.state
79
- ? xstate_1.State.create(options.state)
80
- : initialState);
81
- }), 2), state = _d[0], setState = _d[1];
82
- return [state, service.send, service];
71
+ return [storeSnapshot, service.send, service];
83
72
  }
84
73
  exports.useMachine = useMachine;
@@ -2,73 +2,39 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useSelector = void 0;
4
4
  var react_1 = require("react");
5
- var use_subscription_1 = require("use-subscription");
6
- var use_isomorphic_layout_effect_1 = require("use-isomorphic-layout-effect");
5
+ var with_selector_1 = require("use-sync-external-store/shim/with-selector");
7
6
  var useActor_1 = require("./useActor");
8
7
  var utils_1 = require("./utils");
9
8
  function isService(actor) {
10
9
  return 'state' in actor && 'machine' in actor;
11
10
  }
12
11
  var defaultCompare = function (a, b) { return a === b; };
13
- var defaultGetSnapshot = function (a) {
14
- return isService(a)
15
- ? (0, utils_1.getServiceSnapshot)(a)
16
- : (0, useActor_1.isActorWithState)(a)
17
- ? a.state
18
- : undefined;
12
+ var defaultGetSnapshot = function (a, initialStateCacheRef) {
13
+ if (isService(a)) {
14
+ // A status of 0 = interpreter not started
15
+ if (a.status === 0 && initialStateCacheRef.current) {
16
+ return initialStateCacheRef.current;
17
+ }
18
+ var snapshot = (0, utils_1.getServiceSnapshot)(a);
19
+ initialStateCacheRef.current = a.status === 0 ? snapshot : null;
20
+ return snapshot;
21
+ }
22
+ return (0, useActor_1.isActorWithState)(a) ? a.state : undefined;
19
23
  };
20
24
  function useSelector(actor, selector, compare, getSnapshot) {
21
25
  if (compare === void 0) { compare = defaultCompare; }
22
- if (getSnapshot === void 0) { getSnapshot = defaultGetSnapshot; }
23
- var latestSelectorRef = (0, react_1.useRef)(selector);
24
- var subscription = (0, react_1.useMemo)(function () {
25
- var snapshot = getSnapshot(actor);
26
- var current = selector(snapshot);
27
- var notifySubscriber;
28
- return {
29
- getSnapshot: function () { return snapshot; },
30
- getCurrentValue: function () { return current; },
31
- setCurrentValue: function (newCurrent) {
32
- current = newCurrent;
33
- notifySubscriber === null || notifySubscriber === void 0 ? void 0 : notifySubscriber();
34
- },
35
- subscribe: function (callback) {
36
- notifySubscriber = callback;
37
- var sub = actor.subscribe(function (emitted) {
38
- snapshot = emitted;
39
- var next = latestSelectorRef.current(emitted);
40
- if (!compare(current, next)) {
41
- current = next;
42
- callback();
43
- }
44
- });
45
- return function () {
46
- sub.unsubscribe();
47
- };
48
- }
49
- };
50
- // intentionally omit `getSnapshot` and `compare`
51
- // - `getSnapshot`: it is only supposed to read the "initial" snapshot of an actor
52
- // - `compare`: is really supposed to be idempotent and the same throughout the lifetime of this hook (the same assumption is made in React Redux v7)
26
+ var initialStateCacheRef = (0, react_1.useRef)(null);
27
+ var subscribe = (0, react_1.useCallback)(function (handleStoreChange) {
28
+ var unsubscribe = actor.subscribe(handleStoreChange).unsubscribe;
29
+ return unsubscribe;
53
30
  }, [actor]);
54
- var currentSelected = (0, use_subscription_1.useSubscription)(subscription);
55
- var currentChanged = false;
56
- if (latestSelectorRef.current !== selector) {
57
- var selected = selector(subscription.getSnapshot());
58
- if (!compare(currentSelected, selected)) {
59
- currentChanged = true;
60
- currentSelected = selected;
61
- }
62
- }
63
- (0, use_isomorphic_layout_effect_1.default)(function () {
64
- latestSelectorRef.current = selector;
65
- // this condition should not be required, but setState bailouts are currently buggy: https://github.com/facebook/react/issues/22654
66
- if (currentChanged) {
67
- // required so we don't cause a rerender by setting state (this could create infinite rerendering loop with inline selectors)
68
- // at the same time we need to update the value within the subscription so new emits can compare against what has been returned to the user as current value
69
- subscription.setCurrentValue(currentSelected);
31
+ var boundGetSnapshot = (0, react_1.useCallback)(function () {
32
+ if (getSnapshot) {
33
+ return getSnapshot(actor);
70
34
  }
71
- });
72
- return currentSelected;
35
+ return defaultGetSnapshot(actor, initialStateCacheRef);
36
+ }, [actor, getSnapshot]);
37
+ var selectedSnapshot = (0, with_selector_1.useSyncExternalStoreWithSelector)(subscribe, boundGetSnapshot, boundGetSnapshot, selector, compare);
38
+ return selectedSnapshot;
73
39
  }
74
40
  exports.useSelector = useSelector;
package/lib/useSpawn.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useSpawn = void 0;
4
- var behaviors_1 = require("xstate/lib/behaviors");
4
+ var xstate_1 = require("xstate");
5
5
  var useConstant_1 = require("./useConstant");
6
6
  /**
7
7
  * React hook that spawns an `ActorRef` with the specified `behavior`.
@@ -12,7 +12,7 @@ var useConstant_1 = require("./useConstant");
12
12
  */
13
13
  function useSpawn(behavior) {
14
14
  var actorRef = (0, useConstant_1.default)(function () {
15
- return (0, behaviors_1.spawnBehavior)(behavior);
15
+ return (0, xstate_1.spawnBehavior)(behavior);
16
16
  });
17
17
  return actorRef;
18
18
  }
package/lib/utils.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Interpreter } from 'xstate';
2
2
  export declare function partition<T, A extends T, B extends T>(items: T[], predicate: (item: T) => item is A): [A[], B[]];
3
3
  export declare function getServiceSnapshot<TService extends Interpreter<any, any, any, any>>(service: TService): TService['state'];
4
+ export declare function shallowEqual(objA: any, objB: any): boolean;
4
5
  //# sourceMappingURL=utils.d.ts.map
package/lib/utils.js CHANGED
@@ -27,7 +27,7 @@ var __values = (this && this.__values) || function(o) {
27
27
  throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
28
28
  };
29
29
  Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.getServiceSnapshot = exports.partition = void 0;
30
+ exports.shallowEqual = exports.getServiceSnapshot = exports.partition = void 0;
31
31
  function partition(items, predicate) {
32
32
  var e_1, _a;
33
33
  var _b = __read([[], []], 2), truthy = _b[0], falsy = _b[1];
@@ -56,3 +56,34 @@ function getServiceSnapshot(service) {
56
56
  return service.status !== 0 ? service.state : service.machine.initialState;
57
57
  }
58
58
  exports.getServiceSnapshot = getServiceSnapshot;
59
+ // From https://github.com/reduxjs/react-redux/blob/master/src/utils/shallowEqual.ts
60
+ function is(x, y) {
61
+ if (x === y) {
62
+ return x !== 0 || y !== 0 || 1 / x === 1 / y;
63
+ }
64
+ else {
65
+ return x !== x && y !== y;
66
+ }
67
+ }
68
+ function shallowEqual(objA, objB) {
69
+ if (is(objA, objB))
70
+ return true;
71
+ if (typeof objA !== 'object' ||
72
+ objA === null ||
73
+ typeof objB !== 'object' ||
74
+ objB === null) {
75
+ return false;
76
+ }
77
+ var keysA = Object.keys(objA);
78
+ var keysB = Object.keys(objB);
79
+ if (keysA.length !== keysB.length)
80
+ return false;
81
+ for (var i = 0; i < keysA.length; i++) {
82
+ if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
83
+ !is(objA[keysA[i]], objB[keysA[i]])) {
84
+ return false;
85
+ }
86
+ }
87
+ return true;
88
+ }
89
+ exports.shallowEqual = shallowEqual;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xstate/react",
3
- "version": "2.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "XState tools for React",
5
5
  "keywords": [
6
6
  "state",
@@ -52,9 +52,9 @@
52
52
  "url": "https://github.com/statelyai/xstate/issues"
53
53
  },
54
54
  "peerDependencies": {
55
- "@xstate/fsm": "^1.6.4",
56
- "react": "^16.8.0 || ^17.0.0",
57
- "xstate": "^4.29.0"
55
+ "@xstate/fsm": "^2.0.0",
56
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
57
+ "xstate": "^4.33.0"
58
58
  },
59
59
  "peerDependenciesMeta": {
60
60
  "@xstate/fsm": {
@@ -66,23 +66,24 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "use-isomorphic-layout-effect": "^1.0.0",
69
- "use-subscription": "^1.3.0"
69
+ "use-sync-external-store": "^1.0.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@rollup/plugin-commonjs": "^17.0.0",
73
73
  "@rollup/plugin-node-resolve": "^11.0.1",
74
- "@testing-library/react": "^8.0.9",
74
+ "@testing-library/react": "^13.0.0",
75
75
  "@types/jsdom": "^12.2.3",
76
- "@types/react": "^16.9.11",
77
- "@types/react-dom": "^16.9.4",
76
+ "@types/react": "^17.0.43",
77
+ "@types/react-dom": "^17.0.14",
78
+ "@types/use-sync-external-store": "^0.0.3",
78
79
  "@xstate/fsm": "*",
79
80
  "jest": "^26.6.3",
80
81
  "jsdom": "^14.0.0",
81
82
  "jsdom-global": "^3.0.2",
82
83
  "lerna-alias": "3.0.3-0",
83
- "react": "^16.12.0",
84
- "react-dom": "^16.12.0",
85
- "rollup": "^2.35.1",
84
+ "react": "^18.0.0",
85
+ "react-dom": "^18.0.0",
86
+ "rollup": "^2.69.0",
86
87
  "rollup-plugin-replace": "^2.2.0",
87
88
  "rollup-plugin-terser": "^5.1.2",
88
89
  "rollup-plugin-typescript2": "^0.30.0",