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

query-keys.mddocs/

0

# Query Keys and Utilities

1

2

Utilities for working with React Query keys and mutations in the tRPC context. These functions provide access to the underlying query key structure and enable advanced cache manipulation and query utilities creation.

3

4

## Capabilities

5

6

### getQueryKey

7

8

Extract query keys for tRPC procedures to work directly with React Query cache.

9

10

```typescript { .api }

11

/**

12

* Extract the query key for a tRPC procedure

13

* @param procedureOrRouter - tRPC procedure or router

14

* @param ...params - Variable parameters: [input?, type?] for query procedures, [] for routers

15

* @returns tRPC query key for use with React Query

16

*/

17

function getQueryKey<TProcedureOrRouter extends ProcedureOrRouter>(

18

procedureOrRouter: TProcedureOrRouter,

19

...params: GetParams<TProcedureOrRouter>

20

): TRPCQueryKey;

21

22

type GetParams<TProcedureOrRouter extends ProcedureOrRouter> =

23

TProcedureOrRouter extends DecoratedQuery<infer $Def>

24

? [input?: GetQueryProcedureInput<$Def['input']>, type?: QueryType]

25

: [];

26

27

type QueryType = 'any' | 'infinite' | 'query';

28

29

type TRPCQueryKey = [

30

readonly string[],

31

{ input?: unknown; type?: Exclude<QueryType, 'any'> }?

32

];

33

34

type GetQueryProcedureInput<TProcedureInput> = TProcedureInput extends { cursor?: any }

35

? GetInfiniteQueryInput<TProcedureInput>

36

: DeepPartial<TProcedureInput> | undefined;

37

```

38

39

**Usage Examples:**

40

41

```typescript

42

import { getQueryKey } from "@trpc/react-query";

43

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

44

45

function QueryKeyExamples() {

46

const queryClient = useQueryClient();

47

48

const keyExamples = {

49

// Get key for specific query

50

getSpecificUserKey: () => {

51

const key = getQueryKey(trpc.user.get, { id: 1 }, 'query');

52

console.log("User query key:", key);

53

// Result: [['user', 'get'], { input: { id: 1 }, type: 'query' }]

54

},

55

56

// Get key for all queries of a procedure

57

getAllUserKeys: () => {

58

const key = getQueryKey(trpc.user.get);

59

console.log("All user keys:", key);

60

// Result: [['user', 'get']]

61

},

62

63

// Get key for infinite queries

64

getInfinitePostsKey: () => {

65

const key = getQueryKey(trpc.posts.list, { limit: 10 }, 'infinite');

66

console.log("Infinite posts key:", key);

67

// Result: [['posts', 'list'], { input: { limit: 10 }, type: 'infinite' }]

68

},

69

70

// Use keys with React Query directly

71

invalidateWithKey: () => {

72

const userKey = getQueryKey(trpc.user.get, { id: 1 });

73

queryClient.invalidateQueries({ queryKey: userKey });

74

},

75

76

// Check if data exists in cache

77

hasDataInCache: (userId: number) => {

78

const key = getQueryKey(trpc.user.get, { id: userId });

79

const data = queryClient.getQueryData(key);

80

return data !== undefined;

81

},

82

};

83

84

return (

85

<div>

86

<button onClick={keyExamples.getSpecificUserKey}>

87

Get Specific User Key

88

</button>

89

<button onClick={keyExamples.invalidateWithKey}>

90

Invalidate with Key

91

</button>

92

</div>

93

);

94

}

95

```

96

97

### getMutationKey

98

99

Extract mutation keys for tRPC mutation procedures.

100

101

```typescript { .api }

102

/**

103

* Extract the mutation key for a tRPC mutation procedure

104

* @param procedure - tRPC mutation procedure

105

* @returns tRPC mutation key for use with React Query

106

*/

107

function getMutationKey<TProcedure extends DecoratedMutation<any>>(

108

procedure: TProcedure

109

): TRPCMutationKey;

110

111

type TRPCMutationKey = [readonly string[]];

112

```

113

114

**Usage Examples:**

115

116

```typescript

117

import { getMutationKey } from "@trpc/react-query";

118

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

119

120

function MutationKeyExamples() {

121

const queryClient = useQueryClient();

122

123

const mutationKeyExamples = {

124

// Get mutation key

125

getUserMutationKey: () => {

126

const key = getMutationKey(trpc.user.create);

127

console.log("User create mutation key:", key);

128

// Result: [['user', 'create']]

129

},

130

131

// Check if mutation is in progress

132

isMutationInProgress: () => {

133

const key = getMutationKey(trpc.user.create);

134

const mutation = queryClient.getMutationCache().find({ mutationKey: key });

135

return mutation?.state.status === 'pending';

136

},

137

138

// Cancel specific mutations

139

cancelMutation: () => {

140

const key = getMutationKey(trpc.user.create);

141

queryClient.getMutationCache().findAll({ mutationKey: key })

142

.forEach(mutation => mutation.destroy());

143

},

144

145

// Get mutation count

146

getMutationCount: () => {

147

const key = getMutationKey(trpc.user.create);

148

return queryClient.getMutationCache().findAll({ mutationKey: key }).length;

149

},

150

};

151

152

return (

153

<div>

154

<button onClick={mutationKeyExamples.getUserMutationKey}>

155

Get Mutation Key

156

</button>

157

<button onClick={mutationKeyExamples.cancelMutation}>

158

Cancel Mutations

159

</button>

160

</div>

161

);

162

}

163

```

164

165

### createTRPCQueryUtils

166

167

Create standalone query utilities without React hooks.

168

169

```typescript { .api }

170

/**

171

* Creates query utilities for imperative tRPC operations outside React components

172

* @param opts - Configuration with tRPC client and React Query client

173

* @returns Utility functions mirroring your router structure

174

*/

175

function createTRPCQueryUtils<TRouter extends AnyRouter>(

176

opts: CreateQueryUtilsOptions<TRouter>

177

): TRPCQueryUtils<TRouter>;

178

179

interface CreateQueryUtilsOptions<TRouter extends AnyRouter> {

180

/** tRPC client instance */

181

client: TRPCClient<TRouter> | TRPCUntypedClient<TRouter>;

182

/** React Query client instance */

183

queryClient: QueryClient;

184

}

185

186

interface TRPCQueryUtils<TRouter> {

187

// Utility methods are generated based on your router structure

188

// Each procedure gets utility methods for cache manipulation

189

}

190

```

191

192

**Usage Examples:**

193

194

```typescript

195

import { createTRPCQueryUtils } from "@trpc/react-query";

196

import { QueryClient } from "@tanstack/react-query";

197

198

// Create utils outside React components

199

const queryClient = new QueryClient();

200

const trpcClient = createTRPCClient({

201

url: "http://localhost:3000/api/trpc",

202

});

203

204

const utils = createTRPCQueryUtils({

205

client: trpcClient,

206

queryClient,

207

});

208

209

// Use in non-React contexts

210

async function backgroundSync() {

211

// Fetch and cache data

212

await utils.user.list.fetch();

213

214

// Invalidate stale data

215

await utils.user.invalidate();

216

217

// Prefetch upcoming data

218

await utils.posts.trending.prefetch();

219

}

220

221

// Service worker usage

222

self.addEventListener('sync', async (event) => {

223

if (event.tag === 'background-sync') {

224

await backgroundSync();

225

}

226

});

227

228

// Node.js script usage

229

async function dataPreprocessing() {

230

const users = await utils.user.list.fetch();

231

232

for (const user of users) {

233

// Process each user

234

await utils.user.analytics.prefetch({ userId: user.id });

235

}

236

237

console.log("Data preprocessing complete");

238

}

239

```

240

241

### Query Key Structure

242

243

Understanding the tRPC query key structure for advanced cache manipulation.

244

245

```typescript { .api }

246

// Query key structure

247

type TRPCQueryKey = [

248

readonly string[], // Procedure path: ['user', 'get']

249

{

250

input?: unknown; // Procedure input

251

type?: 'query' | 'infinite'; // Query type

252

}?

253

];

254

255

// Examples of query keys:

256

// trpc.user.get.useQuery({ id: 1 })

257

// Key: [['user', 'get'], { input: { id: 1 }, type: 'query' }]

258

259

// trpc.posts.list.useInfiniteQuery({ limit: 10 })

260

// Key: [['posts', 'list'], { input: { limit: 10 }, type: 'infinite' }]

261

262

// trpc.user.get.invalidate() (all user.get queries)

263

// Key: [['user', 'get']]

264

265

// trpc.invalidate() (all queries)

266

// Key: []

267

```

268

269

**Usage Examples:**

270

271

```typescript

272

function AdvancedCacheManipulation() {

273

const queryClient = useQueryClient();

274

275

const cacheExamples = {

276

// Find all user queries

277

findAllUserQueries: () => {

278

const queries = queryClient.getQueryCache().findAll({

279

queryKey: [['user']],

280

type: 'active',

281

});

282

return queries;

283

},

284

285

// Find specific user data queries

286

findUserDataQueries: (userId: number) => {

287

const queries = queryClient.getQueryCache().findAll({

288

queryKey: [['user', 'get']],

289

predicate: (query) => {

290

const [, params] = query.queryKey as TRPCQueryKey;

291

return params?.input?.id === userId;

292

},

293

});

294

return queries;

295

},

296

297

// Find all infinite queries

298

findInfiniteQueries: () => {

299

const queries = queryClient.getQueryCache().findAll({

300

predicate: (query) => {

301

const [, params] = query.queryKey as TRPCQueryKey;

302

return params?.type === 'infinite';

303

},

304

});

305

return queries;

306

},

307

308

// Custom cache invalidation

309

invalidateUserRelatedQueries: (userId: number) => {

310

queryClient.invalidateQueries({

311

predicate: (query) => {

312

const [path, params] = query.queryKey as TRPCQueryKey;

313

314

// Invalidate user-specific queries

315

if (path[0] === 'user' && params?.input?.id === userId) {

316

return true;

317

}

318

319

// Invalidate user's posts

320

if (path.join('.') === 'posts.byUser' && params?.input?.userId === userId) {

321

return true;

322

}

323

324

return false;

325

},

326

});

327

},

328

};

329

330

return (

331

<div>

332

<button onClick={() => cacheExamples.invalidateUserRelatedQueries(1)}>

333

Invalidate User 1 Related Queries

334

</button>

335

</div>

336

);

337

}

338

```

339

340

### Advanced Query Filtering

341

342

Use query keys for sophisticated cache filtering and manipulation.

343

344

```typescript

345

function QueryFiltering() {

346

const queryClient = useQueryClient();

347

348

const filteringExamples = {

349

// Remove stale user queries

350

removeStaleUserQueries: () => {

351

const staleTime = 5 * 60 * 1000; // 5 minutes

352

const now = Date.now();

353

354

queryClient.getQueryCache().findAll({

355

queryKey: [['user']],

356

predicate: (query) => {

357

const dataUpdatedAt = query.state.dataUpdatedAt;

358

return now - dataUpdatedAt > staleTime;

359

},

360

}).forEach(query => {

361

queryClient.removeQueries({ queryKey: query.queryKey });

362

});

363

},

364

365

// Get query statistics

366

getQueryStatistics: () => {

367

const allQueries = queryClient.getQueryCache().getAll();

368

const trpcQueries = allQueries.filter(query =>

369

Array.isArray(query.queryKey[0])

370

);

371

372

const stats = {

373

total: trpcQueries.length,

374

success: trpcQueries.filter(q => q.state.status === 'success').length,

375

error: trpcQueries.filter(q => q.state.status === 'error').length,

376

loading: trpcQueries.filter(q => q.state.status === 'pending').length,

377

};

378

379

return stats;

380

},

381

382

// Find queries by input pattern

383

findQueriesByInputPattern: (pattern: any) => {

384

return queryClient.getQueryCache().findAll({

385

predicate: (query) => {

386

const [, params] = query.queryKey as TRPCQueryKey;

387

return JSON.stringify(params?.input).includes(JSON.stringify(pattern));

388

},

389

});

390

},

391

};

392

393

const stats = filteringExamples.getQueryStatistics();

394

395

return (

396

<div>

397

<p>Total tRPC queries: {stats.total}</p>

398

<p>Success: {stats.success}</p>

399

<p>Error: {stats.error}</p>

400

<p>Loading: {stats.loading}</p>

401

402

<button onClick={filteringExamples.removeStaleUserQueries}>

403

Remove Stale User Queries

404

</button>

405

</div>

406

);

407

}

408

```

409

410

## Common Patterns

411

412

### Cache Debugging

413

414

```typescript

415

function CacheDebugging() {

416

const queryClient = useQueryClient();

417

418

const debugCache = () => {

419

const cache = queryClient.getQueryCache().getAll();

420

421

console.group("tRPC Query Cache Debug");

422

cache.forEach((query) => {

423

if (Array.isArray(query.queryKey[0])) {

424

const [path, params] = query.queryKey as TRPCQueryKey;

425

console.log({

426

procedure: path.join('.'),

427

input: params?.input,

428

type: params?.type,

429

status: query.state.status,

430

dataUpdatedAt: new Date(query.state.dataUpdatedAt),

431

data: query.state.data,

432

});

433

}

434

});

435

console.groupEnd();

436

};

437

438

return (

439

<button onClick={debugCache}>

440

Debug Cache

441

</button>

442

);

443

}

444

```

445

446

### Query Key Utilities

447

448

```typescript

449

function QueryKeyUtilities() {

450

// Helper functions for working with tRPC query keys

451

const keyUtils = {

452

// Check if a key matches a procedure

453

matchesProcedure: (key: TRPCQueryKey, procedurePath: string[]) => {

454

const [path] = key;

455

return path.length >= procedurePath.length &&

456

procedurePath.every((segment, index) => path[index] === segment);

457

},

458

459

// Extract procedure path from key

460

getProcedurePath: (key: TRPCQueryKey) => {

461

const [path] = key;

462

return path.join('.');

463

},

464

465

// Check if key has specific input

466

hasInput: (key: TRPCQueryKey, input: any) => {

467

const [, params] = key;

468

return JSON.stringify(params?.input) === JSON.stringify(input);

469

},

470

471

// Get query type from key

472

getQueryType: (key: TRPCQueryKey) => {

473

const [, params] = key;

474

return params?.type || 'query';

475

},

476

};

477

478

return keyUtils;

479

}

480

```

481

482

### Batch Operations

483

484

```typescript

485

function BatchOperations() {

486

const queryClient = useQueryClient();

487

488

const batchOps = {

489

// Batch invalidate multiple procedures

490

batchInvalidate: async (procedures: Array<{ path: string[], input?: any }>) => {

491

await Promise.all(

492

procedures.map(({ path, input }) => {

493

const key = input

494

? [path, { input }] as TRPCQueryKey

495

: [path] as TRPCQueryKey;

496

return queryClient.invalidateQueries({ queryKey: key });

497

})

498

);

499

},

500

501

// Batch prefetch related data

502

batchPrefetch: async (userId: number) => {

503

const utils = createTRPCQueryUtils({

504

client: trpcClient,

505

queryClient,

506

});

507

508

await Promise.all([

509

utils.user.get.prefetch({ id: userId }),

510

utils.user.posts.prefetch({ userId }),

511

utils.user.followers.prefetch({ userId }),

512

utils.user.settings.prefetch({ userId }),

513

]);

514

},

515

};

516

517

return (

518

<button onClick={() => batchOps.batchPrefetch(1)}>

519

Batch Prefetch User Data

520

</button>

521

);

522

}

523

```