-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
index.ts
170 lines (154 loc) · 4.88 KB
/
index.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
167
168
169
170
import { useCallback, useRef } from 'react'
import useSWR, { useSWRConfig } from '../core'
import type { Middleware, Key } from '../_internal'
import { useStateWithDeps, startTransition } from './state'
import {
serialize,
withMiddleware,
useIsomorphicLayoutEffect,
UNDEFINED,
getTimestamp,
mergeObjects
} from '../_internal'
import type {
SWRMutationConfiguration,
SWRMutationResponse,
SWRMutationHook,
MutationFetcher,
TriggerWithArgs,
TriggerWithoutArgs,
TriggerWithOptionsArgs
} from './types'
const mutation = (<Data, Error>() =>
(
key: Key,
fetcher: MutationFetcher<Data>,
config: SWRMutationConfiguration<Data, Error> = {}
) => {
const { mutate } = useSWRConfig()
const keyRef = useRef(key)
const fetcherRef = useRef(fetcher)
const configRef = useRef(config)
// Ditch all mutation results that happened earlier than this timestamp.
const ditchMutationsUntilRef = useRef(0)
const [stateRef, stateDependencies, setState] = useStateWithDeps({
data: UNDEFINED,
error: UNDEFINED,
isMutating: false
})
const currentState = stateRef.current
const trigger = useCallback(
async (arg: any, opts?: SWRMutationConfiguration<Data, Error>) => {
const [serializedKey, resolvedKey] = serialize(keyRef.current)
if (!fetcherRef.current) {
throw new Error('Can’t trigger the mutation: missing fetcher.')
}
if (!serializedKey) {
throw new Error('Can’t trigger the mutation: missing key.')
}
// Disable cache population by default.
const options = mergeObjects(
mergeObjects(
{ populateCache: false, throwOnError: true },
configRef.current
),
opts
)
// Trigger a mutation, and also track the timestamp. Any mutation that happened
// earlier this timestamp should be ignored.
const mutationStartedAt = getTimestamp()
ditchMutationsUntilRef.current = mutationStartedAt
setState({ isMutating: true })
try {
const data = await mutate<Data>(
serializedKey,
(fetcherRef.current as any)(resolvedKey, { arg }),
// We must throw the error here so we can catch and update the states.
mergeObjects(options, { throwOnError: true })
)
// If it's reset after the mutation, we don't broadcast any state change.
if (ditchMutationsUntilRef.current <= mutationStartedAt) {
startTransition(() =>
setState({ data, isMutating: false, error: undefined })
)
options.onSuccess?.(data as Data, serializedKey, options)
}
return data
} catch (error) {
// If it's reset after the mutation, we don't broadcast any state change
// or throw because it's discarded.
if (ditchMutationsUntilRef.current <= mutationStartedAt) {
startTransition(() =>
setState({ error: error as Error, isMutating: false })
)
options.onError?.(error as Error, serializedKey, options)
if (options.throwOnError) {
throw error as Error
}
}
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
const reset = useCallback(() => {
ditchMutationsUntilRef.current = getTimestamp()
setState({ data: UNDEFINED, error: UNDEFINED, isMutating: false })
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useIsomorphicLayoutEffect(() => {
keyRef.current = key
fetcherRef.current = fetcher
configRef.current = config
})
// We don't return `mutate` here as it can be pretty confusing (e.g. people
// calling `mutate` but they actually mean `trigger`).
// And also, `mutate` relies on the useSWR hook to exist too.
return {
trigger,
reset,
get data() {
stateDependencies.data = true
return currentState.data
},
get error() {
stateDependencies.error = true
return currentState.error
},
get isMutating() {
stateDependencies.isMutating = true
return currentState.isMutating
}
}
}) as unknown as Middleware
/**
* A hook to define and manually trigger remote mutations like POST, PUT, DELETE and PATCH use cases.
*
* @link https://swr.vercel.app/docs/mutation
* @example
* ```jsx
* import useSWRMutation from 'swr/mutation'
*
* const {
* data,
* error,
* trigger,
* reset,
* isMutating
* } = useSWRMutation(key, fetcher, options?)
* ```
*/
const useSWRMutation = withMiddleware(
useSWR,
mutation
) as unknown as SWRMutationHook
export default useSWRMutation
export {
SWRMutationConfiguration,
SWRMutationResponse,
SWRMutationHook,
MutationFetcher,
TriggerWithArgs,
TriggerWithoutArgs,
TriggerWithOptionsArgs
}