-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
README.md
374 lines (293 loc) · 9.73 KB
/
README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# @xstate/solid
The [@xstate/solid package](https://github.com/statelyai/xstate/tree/main/packages/xstate-solid) contains utilities for using [XState](https://github.com/statelyai/xstate) with [SolidJS](https://github.com/solidjs/solid).
[[toc]]
## Quick Start
1. Install `xstate` and `@xstate/solid`:
```bash
npm i xstate @xstate/solid
```
**Via CDN**
```html
<script src="https://unpkg.com/@xstate/solid/dist/xstate-solid.umd.min.js"></script>
```
By using the global variable `XStateSolid`
or
```html
<script src="https://unpkg.com/@xstate/solid/dist/xstate-solid-fsm.umd.min.js"></script>
```
By using the global variable `XStateSolidFSM`
2. Import the `useMachine` hook:
```js
import { useMachine } from '@xstate/solid';
import { createMachine } from 'xstate';
const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: {
on: { TOGGLE: 'active' }
},
active: {
on: { TOGGLE: 'inactive' }
}
}
});
export const Toggler = () => {
const [state, send] = useMachine(toggleMachine);
return (
<button onclick={() => send({ type: 'TOGGLE' })}>
{state.value === 'inactive'
? 'Click to activate'
: 'Active! Click to deactivate'}
</button>
);
};
```
## API
### `useMachine(machine, options?)`
A SolidJS hook that interprets the given `machine` and starts a service that runs for the lifetime of the component.
**Arguments**
- `machine` - An [XState machine](https://xstate.js.org/docs/guides/machines.html):
```js
// existing machine
const [state, send] = useMachine(machine);
```
- `options` (optional) - [Interpreter options](https://xstate.js.org/docs/guides/interpretation.html#options) and/or any of the following machine config options: `guards`, `actions`, `services`, `delays`, `context`, `state`.
**Returns** a tuple of `[state, send, service]`:
- `state` - Represents the current state of the machine as an XState `State` object. This is a read-only value that is tracked by SolidJS for granular reactivity.
- `send` - A function that sends events to the running service.
- `service` - The created service.
### `useActor(actor)`
A SolidJS hook that subscribes to emitted changes from an existing [actor](https://xstate.js.org/docs/guides/actors.html).
**Arguments**
- `actor` - an actor-like object that contains `.send(...)` and `.subscribe(...)` methods. Allows [SolidJS Signal](https://www.solidjs.com/docs/latest/api#createsignal) (or function) to dynamically specify an actor.
```js
const [state, send] = useActor(someSpawnedActor);
```
### `createService(machine, options?)`
A SolidJS hook that returns the `service` created from the `machine` with the `options`, if specified. It starts the service and runs it for the lifetime of the component. This is similar to `useMachine`.
`createService` returns a static reference (to just the interpreted machine) which will not rerender when its state changes.
**Arguments**
- `machine` - An [XState machine](https://xstate.js.org/docs/guides/machines.html) or a function that lazily returns a machine.
- `options` (optional) - [Interpreter options](https://xstate.js.org/docs/guides/interpretation.html#options) and/or any of the following machine config options: `guards`, `actions`, `services`, `delays`, `context`, `state`.
```js
import { createService } from '@xstate/solid';
import { someMachine } from '../path/to/someMachine';
const App = () => {
const service = createService(someMachine);
// ...
};
```
With options:
```js
// ...
const App = () => {
const service = createService(someMachine, {
actions: {
/* ... */
}
});
// ...
};
```
### `useMachine(machine)` with `@xstate/fsm`
A SolidJS hook that interprets the given finite state `machine` from [`@xstate/fsm`] and starts a service that runs for the lifetime of the component.
This special `useMachine` hook is imported from `@xstate/solid/lib/fsm`
**Arguments**
- `machine` - An [XState finite state machine (FSM)](https://xstate.js.org/docs/packages/xstate-fsm/).
- `options` - An optional `options` object.
**Returns** a tuple of `[state, send, service]`:
- `state` - Represents the current state of the machine as an `@xstate/fsm` `StateMachine.State` object. This is a read-only value that is tracked by SolidJS for granular reactivity.
- `send` - A function that sends events to the running service.
- `service` - The created `@xstate/fsm` service.
**Example**
```js
import { useEffect } from 'solid-js';
import { useMachine } from '@xstate/solid/lib/fsm';
import { createMachine } from '@xstate/fsm';
const context = {
data: undefined
};
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context,
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
entry: ['load'],
on: {
RESOLVE: {
target: 'success',
actions: assign({
data: (context, event) => event.data
})
}
}
},
success: {}
}
});
const Fetcher = ({
onFetch = () => new Promise((res) => res('some data'))
}) => {
const [state, send] = useMachine(fetchMachine, {
actions: {
load: () => {
onFetch().then((res) => {
send({ type: 'RESOLVE', data: res });
});
}
}
});
return (
<Switch fallback={null}>
<Match when={state.value === 'idle'}>
<button onclick={(_) => send({ send: 'FETCH' })}>Fetch</button>;
</Match>
<Match when={state.value === 'loading'}>
<div>Loading...</div>;
</Match>
<Match when={state.value === 'success'}>
Success! Data: <div data-testid="data">{state.context.data}</div>
</Match>
</Switch>
);
};
```
## Configuring Machines
Existing machines can be configured by passing the machine options as the 2nd argument of `useMachine(machine, options)`.
Example: the `'fetchData'` service and `'notifySuccess'` action are both configurable:
```js
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: {
data: undefined,
error: undefined
},
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
invoke: {
src: 'fetchData',
onDone: {
target: 'success',
actions: assign({
data: (_, event) => event.data
})
},
onError: {
target: 'failure',
actions: assign({
error: (_, event) => event.data
})
}
}
},
success: {
entry: 'notifySuccess',
type: 'final'
},
failure: {
on: {
RETRY: 'loading'
}
}
}
});
const Fetcher = ({ onResolve }) => {
const [state, send] = useMachine(fetchMachine, {
actions: {
notifySuccess: (ctx) => onResolve(ctx.data)
},
services: {
fetchData: (_, e) =>
fetch(`some/api/${e.query}`).then((res) => res.json())
}
});
return (
<Switch fallback={null}>
<Match when={state.matches('idle')}>
<button onClick={() => send({ type: 'FETCH', query: 'something' })}>
Search for something
</button>
</Match>
<Match when={state.matches('loading')}>
<div>Searching...</div>
</Match>
<Match when={state.matches('success')}>
<div>Success! Data: {state.context.data}</div>
</Match>
<Match when={state.matches('failure')}>
<div>
<p>{state.context.error.message}</p>
<button onClick={() => send({ type: 'RETRY' })}>Retry</button>
</div>
</Match>
</Switch>
);
};
```
## Matching States
When using [hierarchical](https://xstate.js.org/docs/guides/hierarchical.html) and [parallel](https://xstate.js.org/docs/guides/parallel.html) machines, the state values will be objects, not strings. In this case, it is best to use [`state.matches(...)`](https://xstate.js.org/docs/guides/states.html#state-methods-and-getters).
The SolidJS [Switch and Match Components]() are ideal for this use case:
```jsx
const Loader = () => {
const [state, send] = useMachine(/* ... */);
return (
<div>
<Switch fallback={null}>
<Match when={state.matches('idle')}>
<Loader.Idle />
</Match>
<Match when={state.matches({ loading: 'user' })}>
<Loader.LoadingUser />
</Match>
<Match when={state.matches({ loading: 'friends' })}>
<Loader.LoadingFriends />
</Match>
</Switch>
</div>
);
};
```
## Persisted and Rehydrated State
You can persist and rehydrate state with `useMachine(...)` via `options.state`:
```js
// ...
// Get the persisted state config object from somewhere, e.g. localStorage
const persistedState = JSON.parse(localStorage.getItem('some-persisted-state-key')) || someMachine.initialState;
const App = () => {
const [state, send] = useMachine(someMachine, {
state: persistedState // provide persisted state config object here
});
// state will initially be that persisted state, not the machine's initialState
return (/* ... */)
}
```
## Services
The `service` created in `useMachine(machine)` can be referenced as the third returned value:
```js
// vvvvvvv
const [state, send, service] = useMachine(someMachine);
```
You can subscribe to that service's state changes with the [`createEffect` hook](https://www.solidjs.com/docs/latest/api#createeffect):
```js
// ...
createEffect(() => {
const subscription = service.subscribe((state) => {
// simple state logging
console.log(state);
});
onCleanup(() => subscription.unsubscribe());
}); // note: service should never change
```
Or by using the [`from` utility](https://www.solidjs.com/docs/latest/api#from). Note that this returns a shallow signal and is not deeply reactive
```js
const serviceState = from(service); // Returns an auto updating signal that subscribes/unsubscribes for you
```