forked from TanStack/db
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathusePacedMutations.ts
More file actions
138 lines (131 loc) · 4.16 KB
/
usePacedMutations.ts
File metadata and controls
138 lines (131 loc) · 4.16 KB
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
import { useCallback, useMemo, useRef } from 'react'
import { createPacedMutations } from '@tanstack/db'
import type { PacedMutationsConfig, Transaction } from '@tanstack/db'
/**
* React hook for managing paced mutations with timing strategies.
*
* Provides optimistic mutations with pluggable strategies like debouncing,
* queuing, or throttling. The optimistic updates are applied immediately via
* `onMutate`, and the actual persistence is controlled by the strategy.
*
* @param config - Configuration including onMutate, mutationFn and strategy
* @returns A mutate function that accepts variables and returns Transaction objects
*
* @example
* ```tsx
* // Debounced auto-save
* function AutoSaveForm({ formId }: { formId: string }) {
* const mutate = usePacedMutations<string>({
* onMutate: (value) => {
* // Apply optimistic update immediately
* formCollection.update(formId, draft => {
* draft.content = value
* })
* },
* mutationFn: async ({ transaction }) => {
* await api.save(transaction.mutations)
* },
* strategy: debounceStrategy({ wait: 500 })
* })
*
* const handleChange = async (value: string) => {
* const tx = mutate(value)
*
* // Optional: await persistence or handle errors
* try {
* await tx.isPersisted.promise
* console.log('Saved!')
* } catch (error) {
* console.error('Save failed:', error)
* }
* }
*
* return <textarea onChange={e => handleChange(e.target.value)} />
* }
* ```
*
* @example
* ```tsx
* // Throttled slider updates
* function VolumeSlider() {
* const mutate = usePacedMutations<number>({
* onMutate: (volume) => {
* settingsCollection.update('volume', draft => {
* draft.value = volume
* })
* },
* mutationFn: async ({ transaction }) => {
* await api.updateVolume(transaction.mutations)
* },
* strategy: throttleStrategy({ wait: 200 })
* })
*
* return <input type="range" onChange={e => mutate(+e.target.value)} />
* }
* ```
*
* @example
* ```tsx
* // Debounce with leading/trailing for color picker (persist first + final only)
* function ColorPicker() {
* const mutate = usePacedMutations<string>({
* onMutate: (color) => {
* themeCollection.update('primary', draft => {
* draft.color = color
* })
* },
* mutationFn: async ({ transaction }) => {
* await api.updateTheme(transaction.mutations)
* },
* strategy: debounceStrategy({ wait: 0, leading: true, trailing: true })
* })
*
* return (
* <input
* type="color"
* onChange={e => mutate(e.target.value)}
* />
* )
* }
* ```
*/
export function usePacedMutations<
TVariables = unknown,
T extends object = Record<string, unknown>,
>(
config: PacedMutationsConfig<TVariables, T>,
): (variables: TVariables) => Transaction<T> {
// Keep refs to the latest callbacks so we can call them without recreating the instance
const onMutateRef = useRef(config.onMutate)
onMutateRef.current = config.onMutate
const mutationFnRef = useRef(config.mutationFn)
mutationFnRef.current = config.mutationFn
// Create stable wrappers that always call the latest version
const stableOnMutate = useCallback<typeof config.onMutate>((variables) => {
return onMutateRef.current(variables)
}, [])
const stableMutationFn = useCallback<typeof config.mutationFn>((params) => {
return mutationFnRef.current(params)
}, [])
// Create paced mutations instance with proper dependency tracking
// Serialize strategy for stable comparison since strategy objects are recreated on each render
const mutate = useMemo(() => {
return createPacedMutations<TVariables, T>({
...config,
onMutate: stableOnMutate,
mutationFn: stableMutationFn,
})
}, [
stableOnMutate,
stableMutationFn,
config.metadata,
// Serialize strategy to avoid recreating when object reference changes but values are same
JSON.stringify({
type: config.strategy._type,
options: config.strategy.options,
}),
])
// Return stable mutate callback
const stableMutate = useCallback(mutate, [mutate])
return stableMutate
}