@systemfsoftware/xstate-store-react 0.0.0-stage → 2.1.0-beta.4
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 +5 -0
- package/LICENSE +22 -0
- package/README.md +189 -2
- package/dist/index.d.ts +157 -0
- package/dist/index.mjs +124 -0
- package/package.json +62 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# @systemfsoftware/xstate-store-react
|
|
2
|
+
|
|
3
|
+
# @systemfsoftware/xstate-store-react@2.1.0-beta.4
|
|
4
|
+
|
|
5
|
+
Add `@systemfsoftware/xstate-store-react`, the React bindings for `@systemfsoftware/xstate-store`, forked from statelyai/xstate's `next` branch at 2146ae26 (MIT). It behaves like upstream's 2.1.0-alpha.3. 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,190 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @xstate/store-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> `@systemfsoftware/xstate-store-react` is forked from [statelyai/xstate](https://github.com/statelyai/xstate) (`packages/xstate-store-react`, branch `next` at `2146ae26ebfc7e6a624b3a1f237f9e6ddc30b9f5`), MIT. The text below is upstream's.
|
|
4
|
+
|
|
5
|
+
React adapter for [@xstate/store](https://stately.ai/docs/xstate-store).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
nix build github:systemfsoftware/xstate#xstate-store-react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
See the root README's [Install](../../README.md#install) section for wiring a catalog entry. Its peer `react` comes from your own package manager.
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { createStore, useSelector } from '@xstate/store-react'
|
|
19
|
+
// ...
|
|
20
|
+
|
|
21
|
+
const store = createStore({
|
|
22
|
+
context: { count: 0 },
|
|
23
|
+
on: {
|
|
24
|
+
inc: (ctx) => ({ ...ctx, count: ctx.count + 1 }),
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const App = () => {
|
|
29
|
+
const count = useSelector(store, (s) => s.context.count)
|
|
30
|
+
|
|
31
|
+
return <button onClick={() => store.send({ type: 'inc' })}>Count: {count}</button>
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## API
|
|
36
|
+
|
|
37
|
+
<!-- selector comparison semantics from src/index.ts -->
|
|
38
|
+
|
|
39
|
+
### `useSelector(store, selector?, compare?)`
|
|
40
|
+
|
|
41
|
+
Subscribes to a store and returns a selected value.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { createStore, useSelector } from '@xstate/store-react'
|
|
45
|
+
// ...
|
|
46
|
+
|
|
47
|
+
const store = createStore({
|
|
48
|
+
context: { count: 0 },
|
|
49
|
+
on: {
|
|
50
|
+
inc: (ctx) => ({ ...ctx, count: ctx.count + 1 }),
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const App = () => {
|
|
55
|
+
const count = useSelector(store, (s) => s.context.count)
|
|
56
|
+
// or without selector (returns full snapshot)
|
|
57
|
+
const snapshot = useSelector(store)
|
|
58
|
+
// ...
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Arguments:**
|
|
63
|
+
|
|
64
|
+
- `store` - Store or other readable value created with `createStore()`
|
|
65
|
+
- `selector?` - Function to select a value from snapshot
|
|
66
|
+
- `compare?` - Equality function (default: `===`). Also applies to the full snapshot when `selector` is `undefined`.
|
|
67
|
+
|
|
68
|
+
**Returns:** Selected value (re-renders on change)
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
<!-- useStore overloads and inspection behavior from packages/xstate-store-react/src/index.ts -->
|
|
73
|
+
|
|
74
|
+
### `useStore(definition, options?)`
|
|
75
|
+
|
|
76
|
+
Creates a store instance scoped to a component.
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { useSelector, useStore } from '@xstate/store-react'
|
|
80
|
+
// ...
|
|
81
|
+
|
|
82
|
+
const App = () => {
|
|
83
|
+
const store = useStore({
|
|
84
|
+
context: { count: 0 },
|
|
85
|
+
on: {
|
|
86
|
+
inc: (ctx) => ({ ...ctx, count: ctx.count + 1 }),
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const count = useSelector(store, (s) => s.context.count)
|
|
91
|
+
// ...
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
To wire up an inspector, pass a stable `inspect` callback. The inspector is subscribed while the option is provided and unsubscribed when it is removed or the component unmounts. Stores emit `@xstate.transition` events. With `@statelyai/inspect`, forward these through its public `snapshot()` method; its `inspect` observer accepts the XState v5 inspection protocol.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { createBrowserInspector } from '@statelyai/inspect'
|
|
99
|
+
import type { StoreInspectionEvent } from '@xstate/store'
|
|
100
|
+
|
|
101
|
+
// Outside the component: keep the callback stable between renders.
|
|
102
|
+
const inspector = createBrowserInspector()
|
|
103
|
+
const inspectStore = (event: StoreInspectionEvent) => {
|
|
104
|
+
inspector.snapshot(event.actorRef, event.snapshot, { event: event.event })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Inside the component:
|
|
108
|
+
const store = useStore(
|
|
109
|
+
{
|
|
110
|
+
context: { count: 0 },
|
|
111
|
+
on: {
|
|
112
|
+
inc: (ctx) => ({ ...ctx, count: ctx.count + 1 }),
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{ inspect: inspectStore },
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Arguments:**
|
|
120
|
+
|
|
121
|
+
- `definition` - Store configuration object, or store logic created with `createStoreLogic()` (followed by its `input`)
|
|
122
|
+
- `options?` - Options object:
|
|
123
|
+
- `inspect?` - Observer or callback that receives [inspection events](https://stately.ai/docs/inspection) from the store
|
|
124
|
+
|
|
125
|
+
**Returns:** Store instance (stable across re-renders)
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### `useAtom(atom, selector?, compare?)`
|
|
130
|
+
|
|
131
|
+
Subscribes to an atom and returns its value.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
import { createAtom, useAtom } from '@xstate/store-react'
|
|
135
|
+
// ...
|
|
136
|
+
|
|
137
|
+
const countAtom = createAtom(0)
|
|
138
|
+
|
|
139
|
+
const App = () => {
|
|
140
|
+
const count = useAtom(countAtom)
|
|
141
|
+
|
|
142
|
+
return <button onClick={() => countAtom.set((c) => c + 1)}>{count}</button>
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Arguments:**
|
|
147
|
+
|
|
148
|
+
- `atom` - Atom created with `createAtom()`
|
|
149
|
+
- `selector?` - Selector function
|
|
150
|
+
- `compare?` - Equality function
|
|
151
|
+
|
|
152
|
+
**Returns:** Atom value (re-renders on change)
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### `createStoreHook(definition)`
|
|
157
|
+
|
|
158
|
+
Creates a custom hook that returns `[selectedValue, store]`.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import { createStoreHook } from '@xstate/store-react'
|
|
162
|
+
// ...
|
|
163
|
+
|
|
164
|
+
const useCountStore = createStoreHook({
|
|
165
|
+
context: { count: 0 },
|
|
166
|
+
on: {
|
|
167
|
+
inc: (ctx, e: { by: number }) => ({ ...ctx, count: ctx.count + e.by }),
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const App = () => {
|
|
172
|
+
const [count, store] = useCountStore((s) => s.context.count)
|
|
173
|
+
|
|
174
|
+
return <button onClick={() => store.trigger.inc({ by: 1 })}>{count}</button>
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Arguments:**
|
|
179
|
+
|
|
180
|
+
- `definition` - Store configuration object
|
|
181
|
+
|
|
182
|
+
**Returns:** Custom hook function
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Re-exports
|
|
187
|
+
|
|
188
|
+
All exports from `@xstate/store` are re-exported, including `createStore`, `createAtom`, and more.
|
|
189
|
+
|
|
190
|
+
See the [XState Store docs](https://stately.ai/docs/xstate-store) for the full API, and the [React-specific docs](https://stately.ai/docs/xstate-store#react) for more React examples.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { AnyAtomConfig, AnyStoreConfig, AnyStoreLogicCreator, AtomConfig, BaseAtom, ContextFromStoreConfig, InputFromAtomConfig, InputFromStoreLogicCreator, Observer, Readable, StoreFromStoreConfig, StoreFromStoreLogicCreator, StoreInspectionEvent, StoreSnapshot, ValueFromAtomConfig } from "@systemfsoftware/xstate-store";
|
|
2
|
+
export * from "@systemfsoftware/xstate-store";
|
|
3
|
+
type StoreDefinition = AnyStoreConfig | AnyStoreLogicCreator;
|
|
4
|
+
type StoreFromStoreDefinition<TDefinition extends StoreDefinition> = TDefinition extends AnyStoreLogicCreator ? StoreFromStoreLogicCreator<TDefinition> : TDefinition extends AnyStoreConfig ? StoreFromStoreConfig<TDefinition> : never;
|
|
5
|
+
interface UseStoreOptions {
|
|
6
|
+
/**
|
|
7
|
+
* An observer or callback that receives [inspection
|
|
8
|
+
* events](https://stately.ai/docs/inspection) from the store. Subscribed
|
|
9
|
+
* while provided; stable across re-renders.
|
|
10
|
+
*/
|
|
11
|
+
inspect?: Observer<StoreInspectionEvent> | ((inspectionEvent: StoreInspectionEvent) => void);
|
|
12
|
+
}
|
|
13
|
+
type UseStoreArgs<TDefinition extends StoreDefinition> = TDefinition extends AnyStoreLogicCreator ? undefined extends InputFromStoreLogicCreator<TDefinition> ? [logic: TDefinition, input?: InputFromStoreLogicCreator<TDefinition>, options?: UseStoreOptions] : [logic: TDefinition, input: InputFromStoreLogicCreator<TDefinition>, options?: UseStoreOptions] : [definition: TDefinition, options?: UseStoreOptions];
|
|
14
|
+
type AtomDefinition = BaseAtom<any> | AnyAtomConfig;
|
|
15
|
+
type AtomStateFromDefinition<TDefinition extends AtomDefinition> = TDefinition extends AnyAtomConfig ? readonly [value: ValueFromAtomConfig<TDefinition>, atom: ReturnType<TDefinition['createAtom']>] : TDefinition extends BaseAtom<infer TValue> ? readonly [value: TValue, atom: TDefinition] : never;
|
|
16
|
+
type UseAtomStateArgs<TDefinition extends AtomDefinition> = TDefinition extends AnyAtomConfig ? undefined extends InputFromAtomConfig<TDefinition> ? [config: TDefinition, input?: InputFromAtomConfig<TDefinition>] : [config: TDefinition, input: InputFromAtomConfig<TDefinition>] : [atom: TDefinition];
|
|
17
|
+
type AtomConfigInput<TInput> = undefined extends TInput ? [input?: TInput] : [input: TInput];
|
|
18
|
+
/**
|
|
19
|
+
* A React hook that subscribes to the `store` and selects a value from the
|
|
20
|
+
* store's snapshot via a selector function, with an optional compare function.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* function Component() {
|
|
26
|
+
* const count = useSelector(store, (s) => s.count);
|
|
27
|
+
*
|
|
28
|
+
* return <div>{count}</div>;
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @param store The store, created from `createStore(…)`
|
|
33
|
+
* @param selector A function which takes in the `snapshot` and returns a
|
|
34
|
+
* selected value
|
|
35
|
+
* @param compare An optional function which compares the selected value to the
|
|
36
|
+
* previous value
|
|
37
|
+
* @returns The selected value
|
|
38
|
+
*/
|
|
39
|
+
export declare function useSelector<TSnapshot, T>(store: Readable<TSnapshot>, selector: (snapshot: TSnapshot) => T, compare?: (a: T, b: T) => boolean): T;
|
|
40
|
+
/**
|
|
41
|
+
* A React hook that subscribes to the `store` and selects a value from the
|
|
42
|
+
* store's snapshot via an optional selector function (identity by default),
|
|
43
|
+
* with an optional compare function.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* function Component() {
|
|
49
|
+
* const countSnapshot = useSelector(store);
|
|
50
|
+
*
|
|
51
|
+
* return <div>{countSnapshot.context.count}</div>;
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @param store The store, created from `createStore(…)`
|
|
56
|
+
* @param selector An optional function which takes in the `snapshot` and
|
|
57
|
+
* returns a selected value
|
|
58
|
+
* @param compare An optional function which compares the selected value to the
|
|
59
|
+
* previous value
|
|
60
|
+
* @returns The selected value
|
|
61
|
+
*/
|
|
62
|
+
export declare function useSelector<TSnapshot>(store: Readable<TSnapshot>, selector?: undefined, compare?: (a: TSnapshot | undefined, b: TSnapshot | undefined) => boolean): TSnapshot;
|
|
63
|
+
/**
|
|
64
|
+
* Creates a stable store instance for the lifetime of a React component.
|
|
65
|
+
*
|
|
66
|
+
* Pass `options.inspect` to subscribe an inspector to the store. The
|
|
67
|
+
* subscription is removed when the inspector is removed or on unmount.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* function Counter() {
|
|
73
|
+
* const store = useStore(
|
|
74
|
+
* {
|
|
75
|
+
* context: { count: 0 },
|
|
76
|
+
* on: { inc: (ctx) => ({ count: ctx.count + 1 }) }
|
|
77
|
+
* },
|
|
78
|
+
* { inspect: inspector.inspect }
|
|
79
|
+
* );
|
|
80
|
+
* // …
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function useStore<TDefinition extends StoreDefinition>(...args: UseStoreArgs<TDefinition>): StoreFromStoreDefinition<TDefinition>;
|
|
85
|
+
/**
|
|
86
|
+
* A React hook that subscribes to the `atom` and returns the current value of
|
|
87
|
+
* the atom.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* const atom = createAtom(0);
|
|
93
|
+
*
|
|
94
|
+
* const Component = () => {
|
|
95
|
+
* const count = useAtom(atom);
|
|
96
|
+
*
|
|
97
|
+
* return (
|
|
98
|
+
* <div>
|
|
99
|
+
* <div>{count}</div>
|
|
100
|
+
* <button onClick={() => atom.set((c) => c + 1)}>Increment</button>
|
|
101
|
+
* </div>
|
|
102
|
+
* );
|
|
103
|
+
* };
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @param atom The atom, created from `createAtom(…)`
|
|
107
|
+
* @param selector An optional function which takes in the `snapshot` and
|
|
108
|
+
* returns a selected value
|
|
109
|
+
* @param compare An optional function which compares the selected value to the
|
|
110
|
+
* previous value
|
|
111
|
+
*/
|
|
112
|
+
export declare function useAtom<T>(atom: BaseAtom<T>): T;
|
|
113
|
+
export declare function useAtom<TValue, TInput>(config: AtomConfig<TValue, TInput>, ...input: AtomConfigInput<TInput>): TValue;
|
|
114
|
+
export declare function useAtom<T, S>(atom: BaseAtom<T>, selector: (snapshot: T) => S, compare?: (a: S, b: S) => boolean): S;
|
|
115
|
+
/**
|
|
116
|
+
* Creates or subscribes to an atom for the lifetime of a React component.
|
|
117
|
+
*
|
|
118
|
+
* Pass an existing atom to receive `[value, atom]`, or pass an atom config
|
|
119
|
+
* created with `createAtomConfig(...)` to create a stable local atom.
|
|
120
|
+
*/
|
|
121
|
+
export declare function useAtomState<TDefinition extends AtomDefinition>(...[definition, input]: UseAtomStateArgs<TDefinition>): AtomStateFromDefinition<TDefinition>;
|
|
122
|
+
/**
|
|
123
|
+
* Creates a custom hook that returns the selected value and the store from a
|
|
124
|
+
* store configuration object.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
*
|
|
128
|
+
* ```ts
|
|
129
|
+
* const useCountStore = createStoreHook({
|
|
130
|
+
* context: { count: 0 },
|
|
131
|
+
* on: {
|
|
132
|
+
* inc: (context, event: { by: number }) => ({
|
|
133
|
+
* ...context,
|
|
134
|
+
* count: context.count + event.by
|
|
135
|
+
* })
|
|
136
|
+
* }
|
|
137
|
+
* });
|
|
138
|
+
*
|
|
139
|
+
* function Component() {
|
|
140
|
+
* const [count, store] = useCountStore(s => s.context.count);
|
|
141
|
+
*
|
|
142
|
+
* return (
|
|
143
|
+
* <div>
|
|
144
|
+
* {count}
|
|
145
|
+
* <button onClick={() => store.trigger.inc({ by: 1 })}>+</button>
|
|
146
|
+
* </div>
|
|
147
|
+
* );
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* @param definition The store configuration object
|
|
152
|
+
* @returns A custom hook that returns [selectedValue, store]
|
|
153
|
+
*/
|
|
154
|
+
export declare function createStoreHook<TDefinition extends AnyStoreConfig>(definition: TDefinition): {
|
|
155
|
+
(): [StoreSnapshot<ContextFromStoreConfig<TDefinition>>, StoreFromStoreConfig<TDefinition>];
|
|
156
|
+
<T>(selector: (snapshot: StoreSnapshot<ContextFromStoreConfig<TDefinition>>) => T, compare?: (a: T, b: T) => boolean): [T, StoreFromStoreConfig<TDefinition>];
|
|
157
|
+
};
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { createStore, isAtom } from "@systemfsoftware/xstate-store";
|
|
2
|
+
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
export * from "@systemfsoftware/xstate-store";
|
|
4
|
+
// src/index.ts
|
|
5
|
+
function defaultCompare(a, b) {
|
|
6
|
+
return a === b;
|
|
7
|
+
}
|
|
8
|
+
function identity(snapshot) {
|
|
9
|
+
return snapshot;
|
|
10
|
+
}
|
|
11
|
+
function useSelectorWithCompare(selector, compare) {
|
|
12
|
+
const previous = useRef(void 0);
|
|
13
|
+
return (snapshot) => {
|
|
14
|
+
const next = selector(snapshot);
|
|
15
|
+
return previous.current !== void 0 && compare(previous.current, next) ? previous.current : previous.current = next;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function createStoreFromDefinition(definition) {
|
|
19
|
+
return createStore(definition);
|
|
20
|
+
}
|
|
21
|
+
function useSelector(store, selector, compare = defaultCompare) {
|
|
22
|
+
const subscribe = useCallback((handleStoreChange) => store.subscribe(handleStoreChange).unsubscribe, [store]);
|
|
23
|
+
const selectorWithCompare = useSelectorWithCompare(selector ?? identity, compare);
|
|
24
|
+
return useSyncExternalStore(subscribe, () => selectorWithCompare(store.get()), () => selectorWithCompare(store.get()));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Creates a stable store instance for the lifetime of a React component.
|
|
28
|
+
*
|
|
29
|
+
* Pass `options.inspect` to subscribe an inspector to the store. The
|
|
30
|
+
* subscription is removed when the inspector is removed or on unmount.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* function Counter() {
|
|
36
|
+
* const store = useStore(
|
|
37
|
+
* {
|
|
38
|
+
* context: { count: 0 },
|
|
39
|
+
* on: { inc: (ctx) => ({ count: ctx.count + 1 }) }
|
|
40
|
+
* },
|
|
41
|
+
* { inspect: inspector.inspect }
|
|
42
|
+
* );
|
|
43
|
+
* // …
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function useStore(...args) {
|
|
48
|
+
const [definition] = args;
|
|
49
|
+
const isLogic = "createStore" in definition;
|
|
50
|
+
const input = isLogic ? args[1] : void 0;
|
|
51
|
+
const options = isLogic ? args[2] : args[1];
|
|
52
|
+
const storeRef = useRef(void 0);
|
|
53
|
+
if (!storeRef.current) storeRef.current = isLogic ? definition.createStore(input) : createStoreFromDefinition(definition);
|
|
54
|
+
const inspectRef = useRef(options?.inspect);
|
|
55
|
+
inspectRef.current = options?.inspect;
|
|
56
|
+
const shouldInspect = options?.inspect !== void 0;
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!inspectRef.current) return;
|
|
59
|
+
return storeRef.current.inspect((inspectionEvent) => {
|
|
60
|
+
const inspect = inspectRef.current;
|
|
61
|
+
if (typeof inspect === "function") inspect(inspectionEvent);
|
|
62
|
+
else inspect?.next?.(inspectionEvent);
|
|
63
|
+
}).unsubscribe;
|
|
64
|
+
}, [shouldInspect]);
|
|
65
|
+
return storeRef.current;
|
|
66
|
+
}
|
|
67
|
+
function useAtom(definition, selectorOrInput, compare = defaultCompare) {
|
|
68
|
+
const atomRef = useRef(void 0);
|
|
69
|
+
if (isAtom(definition)) return useSelector(definition, selectorOrInput ?? identity, compare);
|
|
70
|
+
if (!atomRef.current) atomRef.current = definition.createAtom(selectorOrInput);
|
|
71
|
+
return useSelector(atomRef.current, identity, compare);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Creates or subscribes to an atom for the lifetime of a React component.
|
|
75
|
+
*
|
|
76
|
+
* Pass an existing atom to receive `[value, atom]`, or pass an atom config
|
|
77
|
+
* created with `createAtomConfig(...)` to create a stable local atom.
|
|
78
|
+
*/
|
|
79
|
+
function useAtomState(...[definition, input]) {
|
|
80
|
+
const atomRef = useRef(void 0);
|
|
81
|
+
if (!atomRef.current) atomRef.current = isAtom(definition) ? definition : definition.createAtom(input);
|
|
82
|
+
return [useAtom(atomRef.current), atomRef.current];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Creates a custom hook that returns the selected value and the store from a
|
|
86
|
+
* store configuration object.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
*
|
|
90
|
+
* ```ts
|
|
91
|
+
* const useCountStore = createStoreHook({
|
|
92
|
+
* context: { count: 0 },
|
|
93
|
+
* on: {
|
|
94
|
+
* inc: (context, event: { by: number }) => ({
|
|
95
|
+
* ...context,
|
|
96
|
+
* count: context.count + event.by
|
|
97
|
+
* })
|
|
98
|
+
* }
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* function Component() {
|
|
102
|
+
* const [count, store] = useCountStore(s => s.context.count);
|
|
103
|
+
*
|
|
104
|
+
* return (
|
|
105
|
+
* <div>
|
|
106
|
+
* {count}
|
|
107
|
+
* <button onClick={() => store.trigger.inc({ by: 1 })}>+</button>
|
|
108
|
+
* </div>
|
|
109
|
+
* );
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @param definition The store configuration object
|
|
114
|
+
* @returns A custom hook that returns [selectedValue, store]
|
|
115
|
+
*/
|
|
116
|
+
function createStoreHook(definition) {
|
|
117
|
+
const store = createStoreFromDefinition(definition);
|
|
118
|
+
function useStoreHook(selector, compare = defaultCompare) {
|
|
119
|
+
if (!selector) return [useSelector(store), store];
|
|
120
|
+
return [useSelector(store, selector, compare), store];
|
|
121
|
+
}
|
|
122
|
+
return useStoreHook;
|
|
123
|
+
}
|
|
124
|
+
export { createStoreHook, useAtom, useAtomState, useSelector, useStore };
|
package/package.json
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/xstate-store-react",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"version": "2.1.0-beta.4",
|
|
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-store-react"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/systemfsoftware/xstate/tree/main/packages/xstate-store-react#readme",
|
|
12
|
+
"bugs": "https://github.com/systemfsoftware/xstate/issues",
|
|
13
|
+
"description": "React hooks for XState stores — forked under systemfsoftware from statelyai/xstate (@xstate/store-react at 2146ae26).",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@systemfsoftware/xstate-store": "^5.0.0-beta.6"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": "^19.3.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@effect/tsgo": "0.64.1",
|
|
27
|
+
"@microsoft/api-extractor": "7.59.1",
|
|
28
|
+
"@systemfsoftware/arethetypeswrong-cli": "file:../../.deps/arethetypeswrong-cli.tgz",
|
|
29
|
+
"@systemfsoftware/tsconfig": "file:../../.deps/tsconfig.tgz",
|
|
30
|
+
"@systemfsoftware/vitest": "file:../../.deps/vitest.tgz",
|
|
31
|
+
"@testing-library/react": "16.3.3",
|
|
32
|
+
"@types/node": "24.19.1",
|
|
33
|
+
"@types/react": "19.3.0",
|
|
34
|
+
"@types/react-dom": "19.3.0",
|
|
35
|
+
"happy-dom": "20.14.5",
|
|
36
|
+
"react": "19.3.0",
|
|
37
|
+
"react-dom": "19.3.0",
|
|
38
|
+
"rimraf": "6.1.3",
|
|
39
|
+
"tsdown": "0.23.0",
|
|
40
|
+
"typescript": "7.0.2",
|
|
41
|
+
"vitest": "5.0.3",
|
|
42
|
+
"zod": "4.4.3"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"provenance": true
|
|
47
|
+
},
|
|
48
|
+
"exports": {
|
|
49
|
+
".": {
|
|
50
|
+
"types": "./dist/index.d.ts",
|
|
51
|
+
"default": "./dist/index.mjs"
|
|
52
|
+
},
|
|
53
|
+
"./package.json": "./package.json"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"clean": "rimraf dist",
|
|
57
|
+
"build": "tsdown -l warn && pnpm api:check",
|
|
58
|
+
"typecheck": "tsc -b",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"lint:tsgo": "effect-tsgo diagnostics --project tsconfig.app.json --format ${TSGO_FORMAT:-text} && effect-tsgo diagnostics --project tsconfig.test.json --format ${TSGO_FORMAT:-text}",
|
|
61
|
+
"attw": "attw --pack .",
|
|
62
|
+
"api:check": "api-extractor run",
|
|
63
|
+
"api:update": "api-extractor run --local"
|
|
64
|
+
}
|
|
6
65
|
}
|