-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmachine.ts
166 lines (145 loc) · 4.79 KB
/
machine.ts
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
import type { Events, States, Context, InitialContext } from './types'
import { fetchPeople } from '../fakeServer'
import { assign, createMachine } from 'xstate'
import { inspect } from '@xstate/inspect'
if (process.env.NODE_ENV === 'development') {
inspect({
url: 'https://statecharts.io/inspect',
iframe: false,
})
}
export const initialContext: InitialContext = {
people: [],
filter: {
query: '',
employment: ['employee', 'contractor'],
},
debounceFilter: undefined,
fetchErrors: [],
fetching: false,
}
export const machine = createMachine<Context, Events, States>(
{
id: 'people',
strict: true,
initial: 'idle',
context: initialContext,
states: {
// INITIAL STATE
// The machine start still, allowing external control over the first fetch
idle: { on: { START: 'fetch' } },
// FETCH STATES
debounceFetch: {
on: {
// Changing the filters reset the debounce
SET_QUERY: { actions: 'setQuery', target: 'debounceFetch' },
SET_EMPLOYMENT: { actions: 'setEmployment', target: 'debounceFetch' },
},
after: {
debounceDelay: {
target: 'fetch',
actions: ['swapNextFilter'],
},
},
},
fetch: {
on: {
// Changing the filters reset the debounce and cancel the previous fetch, if any
SET_QUERY: { actions: 'setQuery', target: 'debounceFetch' },
SET_EMPLOYMENT: { actions: 'setEmployment', target: 'debounceFetch' },
// Fetch completion management
SUCCESS: { actions: 'setSuccessData', target: 'success' },
FAILURE: { actions: 'setErrorData', target: 'failure' },
},
entry: 'setFetchingData',
invoke: { src: 'fetchPeople' },
},
// SUCCESS STATE
success: {
on: {
// Changing the filters start the debounced fetch
SET_QUERY: { actions: 'setQuery', target: 'debounceFetch' },
SET_EMPLOYMENT: { actions: 'setEmployment', target: 'debounceFetch' },
},
},
// ERROR STATE
failure: {
on: {
// After a failure, retying with the same filter happens immediately
RETRY: { target: 'fetch' },
// Changing the filters start the debounced fetch
SET_QUERY: { actions: 'setQuery', target: 'debounceFetch' },
SET_EMPLOYMENT: { actions: 'setEmployment', target: 'debounceFetch' },
},
},
},
},
{
services: {
// Fetch the new people and return a cancellation method, useful for subsequent, debounced, fetches
fetchPeople: context => send => {
const { load, cancel } = fetchPeople(context.filter)
load
.then(data => send({ type: 'SUCCESS', data }))
.catch((error: any) =>
send({
type: 'FAILURE',
data: !!error.errorMessage
? { errorMessage: error.errorMessage }
: { errorMessage: error.stack },
}),
)
return cancel
},
},
actions: {
// Reset the previous fetch data and store the last fetched data
setSuccessData: assign({
people: (_ctx, event) => (event.type === 'SUCCESS' ? event.data.people : []),
fetchErrors: _ctx => [],
fetching: _ctx => false,
}),
// Reset the previous fetch data and piles up the received error
setErrorData: assign({
people: _ctx => [],
fetchErrors: (ctx, event) => [
...ctx.fetchErrors,
...(event.type === 'FAILURE' ? [event.data] : []),
],
fetching: _ctx => false,
}),
// Store that the machine is fetching
setFetchingData: assign({
fetching: _ctx => true,
}),
// Change the filters of the next fetch
setEmployment: assign({
debounceFilter: (ctx, event) => {
const prev = ctx.debounceFilter || ctx.filter
const next =
event.type === 'SET_EMPLOYMENT' ? { ...prev, employment: event.employment } : prev
return next
},
}),
setQuery: assign({
debounceFilter: (ctx, event) => {
const prev = ctx.debounceFilter || ctx.filter
const next = event.type === 'SET_QUERY' ? { ...prev, query: event.query } : prev
return next
},
}),
// Empty the debounced filter and set the one to be used for fetching the new data
swapNextFilter: assign({
filter: ctx => {
// TODO: how to tell XState that the context contains the debounceFilter?
if (!ctx.debounceFilter) throw new Error('Missing ctx.debounceFilter')
return ctx.debounceFilter
},
debounceFilter: ctx => undefined,
}),
},
delays: {
debounceDelay: () => 500,
},
},
)