or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

helpers.mdindex.mdmutations.mdplugin-setup.mdqueries.mdquery-client.mdstatus-utilities.md

mutations.mddocs/

0

# Data Mutations

1

2

Composables for creating, updating, and deleting data with optimistic updates, automatic query invalidation, and reactive state management.

3

4

## Capabilities

5

6

### useMutation

7

8

Main composable for data mutations with automatic error handling and query invalidation.

9

10

```typescript { .api }

11

/**

12

* Main composable for data mutations with optimistic updates

13

* @param options - Mutation configuration options with Vue reactivity support

14

* @param queryClient - Optional query client instance

15

* @returns Reactive mutation state and execution functions

16

*/

17

function useMutation<TData, TError, TVariables, TContext>(

18

options: UseMutationOptions<TData, TError, TVariables, TContext>,

19

queryClient?: QueryClient

20

): UseMutationReturnType<TData, TError, TVariables, TContext>;

21

22

interface UseMutationOptions<TData, TError, TVariables, TContext> {

23

mutationFn?: MaybeRefOrGetter<

24

(variables: TVariables) => Promise<TData> | TData

25

>;

26

mutationKey?: MaybeRefOrGetter<MutationKey>;

27

onMutate?: MaybeRefOrGetter<

28

(variables: TVariables) => Promise<TContext | void> | TContext | void

29

>;

30

onSuccess?: MaybeRefOrGetter<

31

(data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown

32

>;

33

onError?: MaybeRefOrGetter<

34

(error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown

35

>;

36

onSettled?: MaybeRefOrGetter<

37

(data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown

38

>;

39

retry?: MaybeRefOrGetter<boolean | number | ((failureCount: number, error: TError) => boolean)>;

40

retryDelay?: MaybeRefOrGetter<number | ((retryAttempt: number, error: TError) => number)>;

41

throwOnError?: MaybeRefOrGetter<boolean | ((error: TError) => boolean)>;

42

meta?: MaybeRefOrGetter<MutationMeta>;

43

shallow?: boolean;

44

}

45

46

interface UseMutationReturnType<TData, TError, TVariables, TContext> {

47

data: Ref<TData | undefined>;

48

error: Ref<TError | null>;

49

failureCount: Ref<number>;

50

failureReason: Ref<TError | null>;

51

isError: Ref<boolean>;

52

isIdle: Ref<boolean>;

53

isPending: Ref<boolean>;

54

isPaused: Ref<boolean>;

55

isSuccess: Ref<boolean>;

56

mutate: (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => void;

57

mutateAsync: (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => Promise<TData>;

58

reset: () => void;

59

status: Ref<MutationStatus>;

60

submittedAt: Ref<number>;

61

variables: Ref<TVariables | undefined>;

62

}

63

64

interface MutateOptions<TData, TError, TVariables, TContext> {

65

onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;

66

onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;

67

onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;

68

}

69

```

70

71

**Usage Examples:**

72

73

```typescript

74

import { useMutation, useQueryClient } from '@tanstack/vue-query';

75

76

// Basic mutation

77

const { mutate, isPending, error } = useMutation({

78

mutationFn: (newPost) =>

79

fetch('/api/posts', {

80

method: 'POST',

81

headers: { 'Content-Type': 'application/json' },

82

body: JSON.stringify(newPost)

83

}).then(res => res.json()),

84

onSuccess: () => {

85

console.log('Post created successfully!');

86

},

87

onError: (error) => {

88

console.error('Failed to create post:', error);

89

}

90

});

91

92

// Trigger mutation

93

const createPost = () => {

94

mutate({

95

title: 'New Post',

96

content: 'This is the content'

97

});

98

};

99

100

// Mutation with query invalidation

101

const queryClient = useQueryClient();

102

const { mutate: updateUser } = useMutation({

103

mutationFn: ({ id, data }) =>

104

fetch(`/api/users/${id}`, {

105

method: 'PUT',

106

headers: { 'Content-Type': 'application/json' },

107

body: JSON.stringify(data)

108

}).then(res => res.json()),

109

onSuccess: (data, variables) => {

110

// Invalidate and refetch user queries

111

queryClient.invalidateQueries({ queryKey: ['users'] });

112

queryClient.invalidateQueries({ queryKey: ['user', variables.id] });

113

}

114

});

115

116

// Optimistic updates

117

const { mutate: toggleTodo } = useMutation({

118

mutationFn: ({ id, completed }) =>

119

fetch(`/api/todos/${id}`, {

120

method: 'PATCH',

121

headers: { 'Content-Type': 'application/json' },

122

body: JSON.stringify({ completed })

123

}).then(res => res.json()),

124

onMutate: async ({ id, completed }) => {

125

// Cancel outgoing refetches

126

await queryClient.cancelQueries({ queryKey: ['todos'] });

127

128

// Snapshot previous value

129

const previousTodos = queryClient.getQueryData(['todos']);

130

131

// Optimistically update

132

queryClient.setQueryData(['todos'], (old) =>

133

old?.map(todo =>

134

todo.id === id ? { ...todo, completed } : todo

135

)

136

);

137

138

// Return context for rollback

139

return { previousTodos };

140

},

141

onError: (err, variables, context) => {

142

// Rollback on error

143

if (context?.previousTodos) {

144

queryClient.setQueryData(['todos'], context.previousTodos);

145

}

146

},

147

onSettled: () => {

148

// Always refetch after error or success

149

queryClient.invalidateQueries({ queryKey: ['todos'] });

150

}

151

});

152

153

// Async mutation with error handling

154

const { mutateAsync: deletePost } = useMutation({

155

mutationFn: (postId) =>

156

fetch(`/api/posts/${postId}`, { method: 'DELETE' })

157

.then(res => {

158

if (!res.ok) throw new Error('Failed to delete');

159

return res.json();

160

})

161

});

162

163

// Using async mutation

164

const handleDelete = async (postId) => {

165

try {

166

await deletePost(postId);

167

router.push('/posts');

168

} catch (error) {

169

alert('Failed to delete post');

170

}

171

};

172

173

// Mutation with retry logic

174

const { mutate: uploadFile } = useMutation({

175

mutationFn: (file) => {

176

const formData = new FormData();

177

formData.append('file', file);

178

return fetch('/api/upload', {

179

method: 'POST',

180

body: formData

181

}).then(res => res.json());

182

},

183

retry: 3,

184

retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),

185

onError: (error, variables, context) => {

186

console.error(`Upload failed after retries:`, error);

187

}

188

});

189

190

// Multiple mutations with shared state

191

const { mutate: saveDraft, isPending: isSaving } = useMutation({

192

mutationFn: (draft) => fetch('/api/drafts', {

193

method: 'POST',

194

headers: { 'Content-Type': 'application/json' },

195

body: JSON.stringify(draft)

196

})

197

});

198

199

const { mutate: publishPost, isPending: isPublishing } = useMutation({

200

mutationFn: (post) => fetch('/api/posts', {

201

method: 'POST',

202

headers: { 'Content-Type': 'application/json' },

203

body: JSON.stringify(post)

204

})

205

});

206

207

const isBusy = computed(() => isSaving.value || isPublishing.value);

208

```

209

210

## Types

211

212

```typescript { .api }

213

// Mutation key type

214

type MutationKey = ReadonlyArray<unknown>;

215

216

// Mutation status type

217

type MutationStatus = 'idle' | 'pending' | 'success' | 'error';

218

219

// Mutation function context

220

interface MutationObserverBaseResult<TData, TError, TVariables, TContext> {

221

context: TContext | undefined;

222

data: TData | undefined;

223

error: TError | null;

224

failureCount: number;

225

failureReason: TError | null;

226

isPaused: boolean;

227

status: MutationStatus;

228

submittedAt: number;

229

variables: TVariables | undefined;

230

}

231

232

// Mutation metadata

233

interface MutationMeta extends Record<string, unknown> {

234

[key: string]: unknown;

235

}

236

237

// Base mutation options

238

interface MutationObserverOptions<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown> {

239

mutationFn?: (variables: TVariables) => Promise<TData> | TData;

240

mutationKey?: MutationKey;

241

onMutate?: (variables: TVariables) => Promise<TContext | void> | TContext | void;

242

onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;

243

onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;

244

onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown;

245

retry?: boolean | number | ((failureCount: number, error: TError) => boolean);

246

retryDelay?: number | ((retryAttempt: number, error: TError) => number);

247

throwOnError?: boolean | ((error: TError) => boolean);

248

meta?: MutationMeta;

249

}

250

251

// Vue-specific mutation options type

252

type UseMutationOptionsBase<TData, TError, TVariables, TContext> =

253

MutationObserverOptions<TData, TError, TVariables, TContext> & ShallowOption;

254

255

// Default error type

256

type DefaultError = Error;

257

```