or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

hook-creation.mdindex.mdmutation-hooks.mdquery-hooks.mdquery-keys.mdquery-utilities.mdreact-server-components.mdserver-side-helpers.mdsubscription-hooks.md

mutation-hooks.mddocs/

0

# Mutation Hooks

1

2

React hooks for data modifications with optimistic updates, error handling, and automatic cache invalidation. These hooks are automatically generated for each mutation procedure in your tRPC router.

3

4

## Capabilities

5

6

### useMutation

7

8

Primary hook for data mutations with comprehensive state management and lifecycle callbacks.

9

10

```typescript { .api }

11

/**

12

* Hook for performing data mutations through tRPC mutation procedures

13

* @param opts - Mutation configuration options including callbacks and settings

14

* @returns Mutation result with mutate functions, loading states, and error information

15

*/

16

procedure.useMutation<TContext = unknown>(

17

opts?: UseTRPCMutationOptions<TInput, TError, TOutput, TContext>

18

): UseTRPCMutationResult<TOutput, TError, TInput, TContext>;

19

20

interface UseTRPCMutationOptions<TInput, TError, TOutput, TContext = unknown>

21

extends Omit<UseMutationOptions<TOutput, TError, TInput, TContext>, 'mutationFn'> {

22

trpc?: TRPCReactRequestOptions;

23

}

24

25

interface UseTRPCMutationResult<TOutput, TError, TInput, TContext>

26

extends UseMutationResult<TOutput, TError, TInput, TContext> {

27

trpc: TRPCHookResult;

28

}

29

```

30

31

**Usage Examples:**

32

33

```typescript

34

import { trpc } from "./utils/trpc";

35

36

function CreateUserForm() {

37

const utils = trpc.useUtils();

38

39

const createUser = trpc.user.create.useMutation({

40

onSuccess: (newUser) => {

41

console.log("User created:", newUser);

42

// Invalidate and refetch user list

43

utils.user.list.invalidate();

44

},

45

onError: (error) => {

46

console.error("Failed to create user:", error.message);

47

},

48

});

49

50

const handleSubmit = (formData: { name: string; email: string }) => {

51

createUser.mutate(formData);

52

};

53

54

return (

55

<form onSubmit={(e) => {

56

e.preventDefault();

57

const formData = new FormData(e.currentTarget);

58

handleSubmit({

59

name: formData.get('name') as string,

60

email: formData.get('email') as string,

61

});

62

}}>

63

<input name="name" placeholder="Name" required />

64

<input name="email" type="email" placeholder="Email" required />

65

<button

66

type="submit"

67

disabled={createUser.isPending}

68

>

69

{createUser.isPending ? "Creating..." : "Create User"}

70

</button>

71

{createUser.error && (

72

<div>Error: {createUser.error.message}</div>

73

)}

74

</form>

75

);

76

}

77

```

78

79

### Optimistic Updates

80

81

Implement optimistic updates to improve user experience by updating the UI immediately before the server responds.

82

83

```typescript { .api }

84

// Optimistic update pattern using onMutate callback

85

interface OptimisticUpdateOptions<TInput, TOutput, TContext> {

86

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

87

onError?: (error: TError, variables: TInput, context: TContext | undefined) => void;

88

onSettled?: (data: TOutput | undefined, error: TError | null, variables: TInput, context: TContext | undefined) => void;

89

}

90

```

91

92

**Usage Examples:**

93

94

```typescript

95

function OptimisticUserUpdate({ userId }: { userId: number }) {

96

const utils = trpc.useUtils();

97

98

const updateUser = trpc.user.update.useMutation({

99

onMutate: async (updateData) => {

100

// Cancel outgoing refetches

101

await utils.user.get.cancel({ id: userId });

102

103

// Get current data

104

const previousUser = utils.user.get.getData({ id: userId });

105

106

// Optimistically update the cache

107

utils.user.get.setData(

108

{ id: userId },

109

(old) => old ? { ...old, ...updateData } : undefined

110

);

111

112

// Return context with previous data for rollback

113

return { previousUser };

114

},

115

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

116

// Rollback on error

117

if (context?.previousUser) {

118

utils.user.get.setData({ id: userId }, context.previousUser);

119

}

120

},

121

onSettled: () => {

122

// Refetch to ensure consistency

123

utils.user.get.invalidate({ id: userId });

124

},

125

});

126

127

const handleUpdate = (data: { name: string }) => {

128

updateUser.mutate({ id: userId, ...data });

129

};

130

131

return (

132

<button onClick={() => handleUpdate({ name: "New Name" })}>

133

Update User

134

</button>

135

);

136

}

137

```

138

139

### Mutation State Management

140

141

Access comprehensive mutation state information for UI feedback.

142

143

```typescript { .api }

144

interface UseTRPCMutationResult<TOutput, TError, TInput, TContext> {

145

// Data and state

146

data: TOutput | undefined;

147

error: TError | null;

148

isPending: boolean;

149

isIdle: boolean;

150

isSuccess: boolean;

151

isError: boolean;

152

153

// Mutation functions

154

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

155

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

156

157

// Reset function

158

reset: () => void;

159

160

// Status information

161

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

162

submittedAt: number;

163

164

// tRPC specific

165

trpc: TRPCHookResult;

166

}

167

```

168

169

**Usage Examples:**

170

171

```typescript

172

function MutationStatusExample() {

173

const deleteUser = trpc.user.delete.useMutation();

174

175

const handleDelete = async (userId: number) => {

176

try {

177

await deleteUser.mutateAsync({ id: userId });

178

alert("User deleted successfully");

179

} catch (error) {

180

console.error("Delete failed:", error);

181

}

182

};

183

184

return (

185

<div>

186

<button

187

onClick={() => handleDelete(1)}

188

disabled={deleteUser.isPending}

189

>

190

Delete User

191

</button>

192

193

{/* Status indicators */}

194

{deleteUser.isPending && <div>Deleting...</div>}

195

{deleteUser.isError && (

196

<div>Error: {deleteUser.error?.message}</div>

197

)}

198

{deleteUser.isSuccess && (

199

<div>User deleted successfully!</div>

200

)}

201

202

{/* Reset mutation state */}

203

{(deleteUser.isError || deleteUser.isSuccess) && (

204

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

205

Reset

206

</button>

207

)}

208

</div>

209

);

210

}

211

```

212

213

### Batch Mutations

214

215

Handle multiple related mutations with proper error handling and state management.

216

217

```typescript

218

function BatchMutationExample() {

219

const utils = trpc.useUtils();

220

const createUser = trpc.user.create.useMutation();

221

const updateSettings = trpc.settings.update.useMutation();

222

223

const handleBatchOperation = async () => {

224

try {

225

// Perform mutations sequentially

226

const newUser = await createUser.mutateAsync({

227

name: "John Doe",

228

email: "john@example.com",

229

});

230

231

await updateSettings.mutateAsync({

232

userId: newUser.id,

233

theme: "dark",

234

});

235

236

// Invalidate related queries

237

utils.user.list.invalidate();

238

utils.settings.get.invalidate();

239

240

alert("Batch operation completed successfully");

241

} catch (error) {

242

console.error("Batch operation failed:", error);

243

}

244

};

245

246

const isLoading = createUser.isPending || updateSettings.isPending;

247

248

return (

249

<button onClick={handleBatchOperation} disabled={isLoading}>

250

{isLoading ? "Processing..." : "Create User & Settings"}

251

</button>

252

);

253

}

254

```

255

256

### File Upload Mutations

257

258

Handle file uploads and form data through tRPC mutations.

259

260

```typescript

261

function FileUploadExample() {

262

const uploadAvatar = trpc.user.uploadAvatar.useMutation({

263

onSuccess: (result) => {

264

console.log("Upload successful:", result.url);

265

},

266

onError: (error) => {

267

console.error("Upload failed:", error.message);

268

},

269

});

270

271

const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {

272

const file = event.target.files?.[0];

273

if (!file) return;

274

275

const formData = new FormData();

276

formData.append('file', file);

277

formData.append('userId', '123');

278

279

uploadAvatar.mutate(formData);

280

};

281

282

return (

283

<div>

284

<input

285

type="file"

286

onChange={handleFileUpload}

287

accept="image/*"

288

disabled={uploadAvatar.isPending}

289

/>

290

{uploadAvatar.isPending && <div>Uploading...</div>}

291

{uploadAvatar.data && (

292

<img src={uploadAvatar.data.url} alt="Uploaded avatar" />

293

)}

294

</div>

295

);

296

}

297

```

298

299

### Global Mutation Settings

300

301

Configure global mutation behavior through the mutation overrides system.

302

303

```typescript

304

// In your tRPC setup

305

const trpc = createTRPCReact<AppRouter>({

306

overrides: {

307

useMutation: {

308

onSuccess: ({ originalFn }) => {

309

// Global success handler

310

console.log("Mutation succeeded globally");

311

312

// Call the original success handler

313

originalFn();

314

315

// Add global success behavior

316

showSuccessToast("Operation completed successfully");

317

},

318

},

319

},

320

});

321

322

function showSuccessToast(message: string) {

323

// Your toast implementation

324

console.log("Toast:", message);

325

}

326

```

327

328

## Common Patterns

329

330

### Loading States

331

332

```typescript

333

function LoadingStateExample() {

334

const mutation = trpc.user.create.useMutation();

335

336

return (

337

<button

338

onClick={() => mutation.mutate({ name: "Test" })}

339

disabled={mutation.isPending}

340

>

341

{mutation.isPending ? (

342

<>

343

<Spinner /> Creating...

344

</>

345

) : (

346

"Create User"

347

)}

348

</button>

349

);

350

}

351

```

352

353

### Error Boundaries

354

355

```typescript

356

function MutationWithErrorBoundary() {

357

const mutation = trpc.user.create.useMutation({

358

onError: (error) => {

359

// Log error for debugging

360

console.error("Mutation error:", error);

361

362

// Report to error tracking service

363

errorService.report(error);

364

},

365

});

366

367

return (

368

<button onClick={() => mutation.mutate({ name: "Test" })}>

369

Create User

370

</button>

371

);

372

}

373

```

374

375

### Cache Invalidation Patterns

376

377

```typescript

378

function CacheInvalidationExample() {

379

const utils = trpc.useUtils();

380

381

const createPost = trpc.post.create.useMutation({

382

onSuccess: () => {

383

// Invalidate all post queries

384

utils.post.invalidate();

385

386

// Invalidate specific user's posts

387

utils.post.byUser.invalidate({ userId: currentUserId });

388

389

// Refetch specific query

390

utils.post.list.refetch();

391

},

392

});

393

394

return (

395

<button onClick={() => createPost.mutate({ title: "New Post" })}>

396

Create Post

397

</button>

398

);

399

}