@systemfsoftware/xstate-react 0.0.0-stage → 7.0.0-beta.5

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/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # @systemfsoftware/xstate-react
2
+
3
+ # @systemfsoftware/xstate-react@7.0.0-beta.5
4
+
5
+ Add `@systemfsoftware/xstate-react`, the React bindings for `@systemfsoftware/xstate`, forked from statelyai/xstate's `next` branch at 2146ae26 (MIT). It behaves like upstream's 7.0.0-alpha.4. Under `exactOptionalPropertyTypes`, optional members that can hold `undefined` declare it explicitly. It has no `effect` dependency or peer: the Effect 4.0.1 peer floor applies only to `@systemfsoftware/xstate-effect` and `@systemfsoftware/xstate-test`.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 David Khourshid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md CHANGED
@@ -1,3 +1,56 @@
1
- # Temporary Holding Version
1
+ # @xstate/react
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > `@systemfsoftware/xstate-react` is forked from [statelyai/xstate](https://github.com/statelyai/xstate) (`packages/xstate-react`, branch `next` at `2146ae26ebfc7e6a624b3a1f237f9e6ddc30b9f5`), MIT. The text below is upstream's.
4
+
5
+ This package contains utilities for using [XState](https://github.com/statelyai/xstate) with [React](https://github.com/facebook/react/).
6
+
7
+ - [Read the full documentation in the XState docs](https://stately.ai/docs/xstate-react).
8
+ - [Read our contribution guidelines](https://github.com/statelyai/xstate/blob/main/CONTRIBUTING.md).
9
+
10
+ ## Quick start
11
+
12
+ 1. Install `@systemfsoftware/xstate-react`:
13
+
14
+ ```bash
15
+ nix build github:systemfsoftware/xstate#xstate-react
16
+ ```
17
+
18
+ See the root README's [Install](../../README.md#install) section for wiring a catalog entry. Its peer `@systemfsoftware/xstate` comes from the same flake (`#xstate`), and `react` from your own package manager.
19
+
20
+ 2. Import the `useMachine` hook:
21
+
22
+ ```tsx
23
+ import { useMachine } from '@xstate/react'
24
+ import { createMachine } from 'xstate'
25
+
26
+ const toggleMachine = createMachine({
27
+ id: 'toggle',
28
+ initial: 'inactive',
29
+ states: {
30
+ inactive: {
31
+ on: { TOGGLE: { target: 'active' } },
32
+ },
33
+ active: {
34
+ on: { TOGGLE: { target: 'inactive' } },
35
+ },
36
+ },
37
+ })
38
+
39
+ export const Toggler = () => {
40
+ const [state, send] = useMachine(toggleMachine)
41
+
42
+ return (
43
+ <button onClick={() => send({ type: 'TOGGLE' })}>
44
+ {state.value === 'inactive'
45
+ ? 'Click to activate'
46
+ : 'Active! Click to deactivate'}
47
+ </button>
48
+ )
49
+ }
50
+ ```
51
+
52
+ ## Observing an actor
53
+
54
+ <!-- observer lifecycle from src/useActorRef.ts -->
55
+
56
+ Pass a snapshot listener or observer as the third argument to `useActorRef(logic, options, observer)`. It subscribes before the actor starts. When the hook replaces the actor (after an external stop, or a Fast Refresh that cannot keep it), the observer follows the replacement, even when the callback identity stays the same. Unmounting unsubscribes the observer and stops the actor.
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ import { Actor, ActorOptions, AnyActorLogic, AnyActorRef, AnyStateMachine, ConditionalRequired, IsNotNever, Observer, RequiredActorOptionsFor, RequiredActorOptionsKeys, SnapshotFrom, StateFrom } from "@systemfsoftware/xstate";
3
+ export declare function createActorContext<TLogic extends AnyActorLogic>(actorLogic: TLogic, actorOptions?: ActorOptions<TLogic>): {
4
+ useSelector: <T>(selector: (snapshot: SnapshotFrom<TLogic>) => T, compare?: (a: T, b: T) => boolean) => T;
5
+ useActorRef: () => Actor<TLogic>;
6
+ Provider: (props: {
7
+ children: React.ReactNode;
8
+ options?: ActorOptions<TLogic>;
9
+ /** @deprecated Use `logic` instead. */
10
+ machine?: never;
11
+ logic?: TLogic;
12
+ }) => React.ReactElement<any, any>;
13
+ };
14
+ export declare function shallowEqual(objA: any, objB: any): boolean;
15
+ export declare function useActor<TLogic extends AnyActorLogic>(logic: TLogic, ...[options]: ConditionalRequired<[options?: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>], IsNotNever<RequiredActorOptionsKeys<TLogic>>>): [SnapshotFrom<TLogic>, Actor<TLogic>['send'], Actor<TLogic>];
16
+ export declare function useActorRef<TLogic extends AnyActorLogic>(machine: TLogic, ...[options, observerOrListener]: IsNotNever<RequiredActorOptionsKeys<TLogic>> extends true ? [options: ActorOptions<TLogic> & RequiredActorOptionsFor<TLogic>, observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)] : [options?: ActorOptions<TLogic>, observerOrListener?: Observer<SnapshotFrom<TLogic>> | ((value: SnapshotFrom<TLogic>) => void)]): Actor<TLogic>;
17
+ export declare function useSelector<TActor extends Pick<AnyActorRef, 'subscribe' | 'getSnapshot'> | undefined, T>(actor: TActor, selector: (snapshot: TActor extends {
18
+ getSnapshot(): infer TSnapshot;
19
+ } ? TSnapshot : undefined) => T, compare?: (a: T, b: T) => boolean): T;
20
+ /** @alias useActor */
21
+ export declare function useMachine<TMachine extends AnyStateMachine>(machine: TMachine, ...[options]: ConditionalRequired<[options?: ActorOptions<TMachine> & RequiredActorOptionsFor<TMachine>], IsNotNever<RequiredActorOptionsKeys<TMachine>>>): [StateFrom<TMachine>, Actor<TMachine>['send'], Actor<TMachine>];
package/dist/index.mjs ADDED
@@ -0,0 +1,155 @@
1
+ import * as React from "react";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { createActor, toObserver } from "@systemfsoftware/xstate";
4
+ import useIsomorphicLayoutEffect from "use-isomorphic-layout-effect";
5
+ import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector";
6
+ import { useSyncExternalStore } from "use-sync-external-store/shim";
7
+ // src/useActorRef.ts
8
+ function useIdleActorRef(logic, ...[options]) {
9
+ const [actorRef, setActorRef] = useState(() => {
10
+ return createActor(logic, options);
11
+ });
12
+ const refreshSignal = useMemo(() => ({}), []);
13
+ const refreshSignalRef = useRef(refreshSignal);
14
+ useIsomorphicLayoutEffect(() => {
15
+ const currentLogic = actorRef.logic;
16
+ refreshSignalRef.current;
17
+ refreshSignalRef.current = refreshSignal;
18
+ if (logic.config === currentLogic.config) {
19
+ currentLogic.sources = logic.sources;
20
+ return;
21
+ }
22
+ });
23
+ return [actorRef, setActorRef];
24
+ }
25
+ function useActorLifecycle(actorRef, setActorRef, createReplacement) {
26
+ const pendingStopsRef = useRef(/* @__PURE__ */ new Map());
27
+ useEffect(() => {
28
+ const cancelPendingStop = pendingStopsRef.current.get(actorRef);
29
+ if (cancelPendingStop) {
30
+ cancelPendingStop();
31
+ pendingStopsRef.current.delete(actorRef);
32
+ }
33
+ if (actorRef._processingStatus === 2 && actorRef.getSnapshot()?.status === "stopped") {
34
+ setActorRef(createReplacement());
35
+ return;
36
+ }
37
+ actorRef.start();
38
+ return () => {
39
+ let canceled = false;
40
+ const cancel = () => {
41
+ canceled = true;
42
+ };
43
+ pendingStopsRef.current.set(actorRef, cancel);
44
+ queueMicrotask(() => {
45
+ if (!canceled) actorRef.stop();
46
+ if (pendingStopsRef.current.get(actorRef) === cancel) pendingStopsRef.current.delete(actorRef);
47
+ });
48
+ };
49
+ }, [actorRef]);
50
+ }
51
+ function useActorRef(machine, ...[options, observerOrListener]) {
52
+ const [actorRef, setActorRef] = useIdleActorRef(machine, ...options === void 0 ? [] : [options]);
53
+ useEffect(() => {
54
+ if (!observerOrListener) return;
55
+ const sub = actorRef.subscribe(toObserver(observerOrListener));
56
+ return () => {
57
+ sub.unsubscribe();
58
+ };
59
+ }, [actorRef, observerOrListener]);
60
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, actorRef.options));
61
+ return actorRef;
62
+ }
63
+ // src/useSelector.ts
64
+ function defaultCompare(a, b) {
65
+ return a === b;
66
+ }
67
+ function useSelector(actor, selector, compare = defaultCompare) {
68
+ const subscribe = useCallback((handleStoreChange) => {
69
+ if (!actor) return () => {};
70
+ const { unsubscribe } = actor.subscribe({
71
+ next: handleStoreChange,
72
+ error: handleStoreChange
73
+ });
74
+ return unsubscribe;
75
+ }, [actor]);
76
+ const boundGetSnapshot = useCallback(() => {
77
+ const snapshot = actor?.getSnapshot();
78
+ if (snapshot && "status" in snapshot && snapshot.status === "error") throw snapshot.error;
79
+ return snapshot;
80
+ }, [actor]);
81
+ return useSyncExternalStoreWithSelector(subscribe, boundGetSnapshot, boundGetSnapshot, selector, compare);
82
+ }
83
+ // src/createActorContext.ts
84
+ function createActorContext(actorLogic, actorOptions) {
85
+ const ReactContext = React.createContext(null);
86
+ const OriginalProvider = ReactContext.Provider;
87
+ function Provider({ children, logic: providedLogic = actorLogic, machine, options: providedOptions }) {
88
+ if (machine) throw new Error(`The "machine" prop has been deprecated. Please use "logic" instead.`);
89
+ const actor = useActorRef(providedLogic, {
90
+ ...actorOptions,
91
+ ...providedOptions
92
+ });
93
+ return React.createElement(OriginalProvider, {
94
+ value: actor,
95
+ children
96
+ });
97
+ }
98
+ Provider.displayName = `ActorProvider`;
99
+ function useContext() {
100
+ const actor = React.useContext(ReactContext);
101
+ if (!actor) throw new Error(`You used a hook from "${Provider.displayName}" but it's not inside a <${Provider.displayName}> component.`);
102
+ return actor;
103
+ }
104
+ function useSelector$1(selector, compare) {
105
+ return useSelector(useContext(), selector, compare);
106
+ }
107
+ return {
108
+ Provider,
109
+ useActorRef: useContext,
110
+ useSelector: useSelector$1
111
+ };
112
+ }
113
+ // src/shallowEqual.ts
114
+ function is(x, y) {
115
+ if (x === y) return x !== 0 || y !== 0 || 1 / x === 1 / y;
116
+ else return x !== x && y !== y;
117
+ }
118
+ function shallowEqual(objA, objB) {
119
+ if (is(objA, objB)) return true;
120
+ if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) return false;
121
+ const keysA = Object.keys(objA);
122
+ const keysB = Object.keys(objB);
123
+ if (keysA.length !== keysB.length) return false;
124
+ for (const key of keysA) if (!Object.prototype.hasOwnProperty.call(objB, key) || !is(objA[key], objB[key])) return false;
125
+ return true;
126
+ }
127
+ // src/useActor.ts
128
+ function useActor(logic, ...[options]) {
129
+ const [actorRef, setActorRef] = useIdleActorRef(logic, ...options === void 0 ? [] : [options]);
130
+ const getSnapshot = useCallback(() => {
131
+ return actorRef.getSnapshot();
132
+ }, [actorRef]);
133
+ const subscribe = useCallback((handleStoreChange) => {
134
+ const { unsubscribe } = actorRef.subscribe({
135
+ next: handleStoreChange,
136
+ error: handleStoreChange
137
+ });
138
+ return unsubscribe;
139
+ }, [actorRef]);
140
+ const actorSnapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
141
+ const snapshotWithStatus = "status" in actorSnapshot ? actorSnapshot : void 0;
142
+ if (snapshotWithStatus?.status === "error") throw snapshotWithStatus.error;
143
+ useActorLifecycle(actorRef, setActorRef, () => createActor(actorRef.logic, options));
144
+ return [
145
+ actorSnapshot,
146
+ actorRef.send,
147
+ actorRef
148
+ ];
149
+ }
150
+ // src/useMachine.ts
151
+ /** @alias useActor */
152
+ function useMachine(machine, ...[options]) {
153
+ return useActor(machine, ...options === void 0 ? [] : [options]);
154
+ }
155
+ export { createActorContext, shallowEqual, useActor, useActorRef, useMachine, useSelector };
package/package.json CHANGED
@@ -1,6 +1,82 @@
1
1
  {
2
2
  "name": "@systemfsoftware/xstate-react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "license": "MIT",
4
+ "version": "7.0.0-beta.5",
5
+ "author": "Ryan Lee <drdgvhbh@gmail.com>",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/systemfsoftware/xstate.git",
9
+ "directory": "packages/xstate-react"
10
+ },
11
+ "homepage": "https://github.com/systemfsoftware/xstate/tree/main/packages/xstate-react#readme",
12
+ "bugs": "https://github.com/systemfsoftware/xstate/issues",
13
+ "description": "React hooks for XState actors — forked under systemfsoftware from statelyai/xstate (@xstate/react at 2146ae26).",
14
+ "type": "module",
15
+ "sideEffects": false,
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "dependencies": {
20
+ "use-isomorphic-layout-effect": "1.2.1",
21
+ "use-sync-external-store": "1.7.0"
22
+ },
23
+ "peerDependencies": {
24
+ "@systemfsoftware/xstate": "^6.0.0-beta.65",
25
+ "react": "^19.3.0"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "@systemfsoftware/xstate": {
29
+ "optional": true
30
+ }
31
+ },
32
+ "devDependencies": {
33
+ "@effect/tsgo": "0.64.1",
34
+ "@microsoft/api-extractor": "7.59.1",
35
+ "@opentelemetry/api": "1.9.1",
36
+ "@systemfsoftware/arethetypeswrong-cli": "file:../../.deps/arethetypeswrong-cli.tgz",
37
+ "@systemfsoftware/tsconfig": "file:../../.deps/tsconfig.tgz",
38
+ "@systemfsoftware/vitest": "file:../../.deps/vitest.tgz",
39
+ "@systemfsoftware/xstate": "^6.0.0-beta.65",
40
+ "@testing-library/react": "16.3.3",
41
+ "@types/node": "24.19.1",
42
+ "@types/react": "19.3.0",
43
+ "@types/react-dom": "19.3.0",
44
+ "@types/use-sync-external-store": "1.7.0",
45
+ "happy-dom": "20.14.5",
46
+ "react": "19.3.0",
47
+ "react-dom": "19.3.0",
48
+ "rimraf": "6.1.3",
49
+ "rxjs": "7.8.2",
50
+ "tsdown": "0.23.0",
51
+ "typescript": "7.0.2",
52
+ "vitest": "5.0.3",
53
+ "zod": "3.25.76"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public",
57
+ "provenance": true
58
+ },
59
+ "imports": {
60
+ "#is-development": {
61
+ "development": "./src/true.ts",
62
+ "default": "./src/false.ts"
63
+ }
64
+ },
65
+ "exports": {
66
+ ".": {
67
+ "types": "./dist/index.d.ts",
68
+ "default": "./dist/index.mjs"
69
+ },
70
+ "./package.json": "./package.json"
71
+ },
72
+ "scripts": {
73
+ "clean": "rimraf dist",
74
+ "build": "tsdown -l warn && pnpm api:check",
75
+ "typecheck": "tsc -b",
76
+ "test": "vitest run",
77
+ "lint:tsgo": "effect-tsgo diagnostics --project tsconfig.app.json --format ${TSGO_FORMAT:-text} && effect-tsgo diagnostics --project tsconfig.test.json --format ${TSGO_FORMAT:-text}",
78
+ "attw": "attw --pack .",
79
+ "api:check": "api-extractor run",
80
+ "api:update": "api-extractor run --local"
81
+ }
6
82
  }