or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context.mderror-handling.mdindex.mdinfinite-queries.mdmutations.mdparallel-queries.mdqueries.mdssr.mdstatus.md

mutations.mddocs/

0

# Mutations

1

2

Data mutation operations with optimistic updates, automatic error handling, and cache invalidation patterns. The `useMutation` hook handles side effects like creating, updating, or deleting data.

3

4

## Capabilities

5

6

### useMutation Hook

7

8

The main hook for performing data mutations with loading states and error handling.

9

10

```typescript { .api }

11

/**

12

* Create a mutation for performing side effects

13

* @param options - Mutation configuration options

14

* @returns Mutation result with mutate functions and states

15

*/

16

function useMutation<TData = unknown, TError = unknown, TVariables = void, TContext = unknown>(

17

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

18

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

19

20

/**

21

* Create a mutation with separate mutationFn parameter

22

* @param mutationFn - Function that performs the mutation

23

* @param options - Additional mutation configuration

24

* @returns Mutation result with mutate functions and states

25

*/

26

function useMutation<TData = unknown, TError = unknown, TVariables = void, TContext = unknown>(

27

mutationFn: MutationFunction<TData, TVariables>,

28

options?: Omit<UseMutationOptions<TData, TError, TVariables, TContext>, 'mutationFn'>

29

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

30

31

/**

32

* Create a mutation with mutation key for tracking and deduplication

33

* @param mutationKey - Unique identifier for the mutation

34

* @param options - Mutation configuration

35

* @returns Mutation result with mutate functions and states

36

*/

37

function useMutation<TData = unknown, TError = unknown, TVariables = void, TContext = unknown>(

38

mutationKey: MutationKey,

39

mutationFn: MutationFunction<TData, TVariables>,

40

options?: Omit<UseMutationOptions<TData, TError, TVariables, TContext>, 'mutationKey' | 'mutationFn'>

41

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

42

```

43

44

**Usage Examples:**

45

46

```typescript

47

import { useMutation, useQueryClient } from "react-query";

48

49

// Basic mutation

50

const createPost = useMutation({

51

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

52

method: 'POST',

53

body: JSON.stringify(newPost)

54

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

55

onSuccess: () => {

56

alert('Post created!');

57

}

58

});

59

60

// Mutation with cache invalidation

61

const updateUser = useMutation({

62

mutationFn: ({ id, data }) => fetch(`/api/users/${id}`, {

63

method: 'PUT',

64

body: JSON.stringify(data)

65

}),

66

onSuccess: (data, variables) => {

67

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

68

}

69

});

70

71

// Optimistic mutation

72

const toggleTodo = useMutation({

73

mutationFn: ({ id, completed }) =>

74

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

75

method: 'PATCH',

76

body: JSON.stringify({ completed })

77

}),

78

onMutate: async (variables) => {

79

// Cancel outgoing refetches

80

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

81

82

// Snapshot previous value

83

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

84

85

// Optimistically update

86

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

87

old.map(todo =>

88

todo.id === variables.id

89

? { ...todo, completed: variables.completed }

90

: todo

91

)

92

);

93

94

return { previousTodos };

95

},

96

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

97

// Rollback on error

98

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

99

},

100

onSettled: () => {

101

// Always refetch after error or success

102

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

103

}

104

});

105

106

// Using the mutations

107

const handleCreatePost = () => {

108

createPost.mutate({ title: 'New Post', content: 'Content' });

109

};

110

111

const handleUpdateAsync = async () => {

112

try {

113

const result = await updateUser.mutateAsync({

114

id: 1,

115

data: { name: 'Updated Name' }

116

});

117

console.log('Update successful:', result);

118

} catch (error) {

119

console.error('Update failed:', error);

120

}

121

};

122

```

123

124

### Mutation Result Interface

125

126

The return value from `useMutation` containing mutation functions and states.

127

128

```typescript { .api }

129

interface UseMutationResult<TData = unknown, TError = unknown, TVariables = unknown, TContext = unknown> {

130

/** Function to trigger the mutation (fire-and-forget) */

131

mutate: UseMutateFunction<TData, TError, TVariables, TContext>;

132

/** Async function to trigger mutation and return promise */

133

mutateAsync: UseMutateAsyncFunction<TData, TError, TVariables, TContext>;

134

/** Data returned from successful mutation */

135

data: TData | undefined;

136

/** Error object if mutation failed */

137

error: TError | null;

138

/** True if mutation is in error state */

139

isError: boolean;

140

/** True if mutation is currently running */

141

isPending: boolean;

142

/** True if mutation succeeded */

143

isSuccess: boolean;

144

/** True if mutation has been triggered at least once */

145

isIdle: boolean;

146

/** Current status of the mutation */

147

status: 'idle' | 'pending' | 'error' | 'success';

148

/** Variables passed to the last mutation call */

149

variables: TVariables | undefined;

150

/** Context returned from onMutate */

151

context: TContext | undefined;

152

/** Number of times mutation has failed */

153

failureCount: number;

154

/** Reason mutation is paused */

155

failureReason: TError | null;

156

/** True if mutation is paused */

157

isPaused: boolean;

158

/** Function to reset mutation state */

159

reset: () => void;

160

}

161

162

type UseMutateFunction<TData = unknown, TError = unknown, TVariables = void, TContext = unknown> = (

163

variables: TVariables,

164

options?: MutateOptions<TData, TError, TVariables, TContext>

165

) => void;

166

167

type UseMutateAsyncFunction<TData = unknown, TError = unknown, TVariables = void, TContext = unknown> = (

168

variables: TVariables,

169

options?: MutateOptions<TData, TError, TVariables, TContext>

170

) => Promise<TData>;

171

```

172

173

### Mutation Options Interface

174

175

Configuration options for `useMutation` hook.

176

177

```typescript { .api }

178

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

179

/** Unique identifier for the mutation */

180

mutationKey?: MutationKey;

181

/** Function that performs the mutation */

182

mutationFn?: MutationFunction<TData, TVariables>;

183

/** Retry configuration */

184

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

185

/** Delay between retries */

186

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

187

/** Callback before mutation starts (for optimistic updates) */

188

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

189

/** Callback on successful mutation */

190

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

191

/** Callback on mutation error */

192

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

193

/** Callback after mutation settles (success or error) */

194

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

195

/** React context for QueryClient */

196

context?: React.Context<QueryClient | undefined>;

197

/** Whether to throw errors to error boundaries */

198

useErrorBoundary?: boolean | ((error: TError, variables: TVariables, context: TContext | undefined) => boolean);

199

/** Additional metadata */

200

meta?: MutationMeta;

201

/** Network mode configuration */

202

networkMode?: 'online' | 'always' | 'offlineFirst';

203

}

204

205

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

206

/** Override onSuccess for this call */

207

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

208

/** Override onError for this call */

209

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

210

/** Override onSettled for this call */

211

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

212

}

213

```

214

215

### Mutation Function Interface

216

217

The function that actually performs the mutation.

218

219

```typescript { .api }

220

type MutationFunction<TData = unknown, TVariables = void> = (

221

variables: TVariables

222

) => Promise<TData>;

223

224

type MutationKey = readonly unknown[];

225

226

type MutationMeta = Record<string, unknown>;

227

```

228

229

## Advanced Usage Patterns

230

231

### Cache Updates

232

233

Different strategies for updating cached data after mutations:

234

235

```typescript

236

const queryClient = useQueryClient();

237

238

// 1. Invalidation (refetch)

239

const mutation = useMutation({

240

mutationFn: updateUser,

241

onSuccess: (data, variables) => {

242

// Invalidate and refetch

243

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

244

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

245

}

246

});

247

248

// 2. Direct cache update

249

const mutation = useMutation({

250

mutationFn: updateUser,

251

onSuccess: (updatedUser, variables) => {

252

// Update specific user query

253

queryClient.setQueryData(['user', variables.id], updatedUser);

254

255

// Update users list

256

queryClient.setQueryData(['users'], (oldUsers) =>

257

oldUsers.map(user =>

258

user.id === variables.id ? updatedUser : user

259

)

260

);

261

}

262

});

263

264

// 3. Add to cache (for create operations)

265

const createMutation = useMutation({

266

mutationFn: createUser,

267

onSuccess: (newUser) => {

268

// Add to users list

269

queryClient.setQueryData(['users'], (oldUsers) => [

270

...oldUsers,

271

newUser

272

]);

273

274

// Set individual user query

275

queryClient.setQueryData(['user', newUser.id], newUser);

276

}

277

});

278

```

279

280

### Global Mutation States

281

282

Monitor all mutations using `useIsMutating`:

283

284

```typescript

285

import { useIsMutating } from "react-query";

286

287

function GlobalLoadingIndicator() {

288

const isMutating = useIsMutating();

289

290

if (isMutating) {

291

return <div>Saving changes...</div>;

292

}

293

294

return null;

295

}

296

297

// Filter specific mutations

298

function UserUpdateIndicator({ userId }: { userId: string }) {

299

const isUpdatingUser = useIsMutating({

300

mutationKey: ['updateUser', userId]

301

});

302

303

return isUpdatingUser ? <div>Updating user...</div> : null;

304

}

305

```

306

307

### Error Recovery

308

309

```typescript

310

const mutation = useMutation({

311

mutationFn: updateData,

312

retry: (failureCount, error) => {

313

// Don't retry client errors (4xx)

314

if (error.status >= 400 && error.status < 500) {

315

return false;

316

}

317

318

// Retry server errors up to 3 times

319

return failureCount < 3;

320

},

321

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

322

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

323

// Handle error

324

toast.error(`Failed to update: ${error.message}`);

325

326

// Log for analytics

327

analytics.track('mutation_error', {

328

mutation: 'updateData',

329

error: error.message,

330

variables

331

});

332

}

333

});

334

335

// Manual retry

336

if (mutation.isError) {

337

return (

338

<div>

339

<p>Update failed: {mutation.error.message}</p>

340

<button onClick={() => mutation.reset()}>

341

Dismiss

342

</button>

343

<button onClick={() => mutation.mutate(lastVariables)}>

344

Retry

345

</button>

346

</div>

347

);

348

}

349

```

350

351

### Sequential Mutations

352

353

Chain mutations together:

354

355

```typescript

356

function useSequentialMutations() {

357

const queryClient = useQueryClient();

358

359

const createUser = useMutation({

360

mutationFn: (userData) => api.createUser(userData)

361

});

362

363

const assignRole = useMutation({

364

mutationFn: ({ userId, roleId }) => api.assignRole(userId, roleId)

365

});

366

367

const sendWelcomeEmail = useMutation({

368

mutationFn: (userId) => api.sendWelcomeEmail(userId)

369

});

370

371

const createUserWithRole = async (userData, roleId) => {

372

try {

373

const user = await createUser.mutateAsync(userData);

374

await assignRole.mutateAsync({ userId: user.id, roleId });

375

await sendWelcomeEmail.mutateAsync(user.id);

376

377

// Refresh users list

378

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

379

380

return user;

381

} catch (error) {

382

// Handle any step failure

383

console.error('User creation process failed:', error);

384

throw error;

385

}

386

};

387

388

return {

389

createUserWithRole,

390

isLoading: createUser.isPending || assignRole.isPending || sendWelcomeEmail.isPending

391

};

392

}

393

```