-
Notifications
You must be signed in to change notification settings - Fork 133
/
useMutation.ts
97 lines (88 loc) · 2.87 KB
/
useMutation.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
import { isRedirect, useRouter } from '@tanstack/react-router'
import * as React from 'react'
type BaseMutationProps<TVariables, TData, TError> = {
fn: (variables: TVariables) => Promise<TData>
onSuccess?: (ctx: {
variables: TVariables
data: TData
}) => void | Promise<void>
onError?: (ctx: { variables: TVariables; error: TError }) => void
onSettled?: (ctx: {
variables: TVariables
data: TData | undefined
error: TError | undefined
}) => void | Promise<void>
}
export function useBaseMutation<TVariables, TData, TError = Error>(
opts: BaseMutationProps<TVariables, TData, TError>
) {
const [submittedAt, setSubmittedAt] = React.useState<number | undefined>()
const [variables, setVariables] = React.useState<TVariables | undefined>()
const [error, setError] = React.useState<TError | undefined>()
const [data, setData] = React.useState<TData | undefined>()
const [status, setStatus] = React.useState<
'idle' | 'pending' | 'success' | 'error'
>('idle')
// eslint-disable-next-line react-hooks/exhaustive-deps
const mutate = React.useCallback(
(async (variables: TVariables): Promise<TData | undefined> => {
setStatus('pending')
setSubmittedAt(Date.now())
setVariables(variables)
//
try {
const data = await opts.fn(variables)
await opts.onSuccess?.({ variables, data })
await opts.onSettled?.({ variables, data, error: undefined })
setStatus('success')
setError(undefined)
setData(data)
return data
} catch (err: any) {
opts.onError?.({ variables, error: err })
opts.onSettled?.({ variables, data: undefined, error: err })
setStatus('error')
setError(err)
}
}) as TVariables extends undefined
? (variables?: TVariables) => Promise<TData | undefined>
: (variables: TVariables) => Promise<TData | undefined>,
// eslint-disable-next-line react-hooks/exhaustive-deps
[opts.fn]
)
const handleSubmit = React.useCallback(
(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
mutate(new FormData((e as any).target) as TVariables)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[mutate, variables]
)
return {
status,
variables,
submittedAt,
mutate,
error,
data,
handleSubmit,
}
}
export function useMutation<TVariables, TData, TError = Error>(
opts: BaseMutationProps<TVariables, TData, TError>
) {
const router = useRouter()
return useBaseMutation<TVariables, TData, TError>({
...opts,
// Use onSettled to handle potential redirects
onSettled: async (ctx) => {
if (isRedirect(ctx.data)) {
router.navigate({ ...(ctx.data as any) })
} else if (isRedirect(ctx.error)) {
router.navigate({ ...(ctx.error as any) })
}
router.invalidate()
await opts.onSettled?.(ctx)
},
})
}