Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/spicy-monkeys-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-react-query": minor
---

Add typesafe setQueryData to OpenapiQueryClient
1 change: 1 addition & 0 deletions docs/.vitepress/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export default defineConfig({
{ text: "useSuspenseQuery", link: "/use-suspense-query" },
{ text: "useInfiniteQuery", link: "/use-infinite-query" },
{ text: "queryOptions", link: "/query-options" },
{ text: "setQueryData", link: "/set-query-data" },
],
},
{
Expand Down
98 changes: 98 additions & 0 deletions docs/openapi-react-query/set-query-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: setQueryData
---

# {{ $frontmatter.title }}

The `setQueryData` method lets you directly update the cached data for a specific query (by method, path, and params) in a type-safe way, just like [QueryClient.setQueryData](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientsetquerydata).

- The updater function is fully type-safe and infers the correct data type from your OpenAPI schema.
- No manual type annotations are needed for the updater argument.
- Works with any query created by this client.

::: tip
You can find more information about `setQueryData` on the [@tanstack/react-query documentation](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientsetquerydata).
:::

## Example

::: code-group

```tsx [src/app.tsx]
import { $api } from './api'

export const App = () => {
// Update the cached list of posts.
const handleAddPost = (newPost) => {
$api.setQueryData(
'get',
'/posts',
(oldPosts = []) => [...oldPosts, newPost],
queryClient
)
}

// Update a single post by id.
const handleEditPost = (updatedPost) => {
$api.setQueryData(
'get',
'/posts/{post_id}',
(oldPost) => ({ ...oldPost, ...updatedPost }),
queryClient,
{ params: { path: { post_id: updatedPost.id } } }
)
}

return null
}
```

```ts [src/api.ts]
import createFetchClient from 'openapi-fetch'
import createClient from 'openapi-react-query'
import type { paths } from './my-openapi-3-schema' // generated by openapi-typescript

const fetchClient = createFetchClient<paths>({
baseUrl: 'https://myapi.dev/v1/',
})
export const $api = createClient(fetchClient)
```

:::

## Type Safety Example

If you try to update the cache with the wrong type, TypeScript will show an error:

```ts [❌ TypeScript Error Example]
$api.setQueryData(
'get',
'/posts',
// ❌ This updater returns a string, but the cached data should be an array of posts.
(oldPosts) => 'not an array', // TypeScript Error: Type 'string' is not assignable to type 'Post[]'.
queryClient
)
```

## Api

```tsx
$api.setQueryData(method, path, updater, queryClient, options?);
```

**Arguments**

- `method` **(required)**
- The HTTP method for the query (e.g. "get").
- `path` **(required)**
- The path for the query (e.g. "/posts/{post_id}").
- `updater` **(required)**
- A function that receives the current cached data and returns the new data. The argument is fully type-inferred from your OpenAPI schema.
- `queryClient` **(required)**
- The [QueryClient](https://tanstack.com/query/latest/docs/framework/react/reference/QueryClient) instance to update.
- `options`
- The fetch options (e.g. params) for the query. Only required if your endpoint requires parameters.

## TypeScript Inference

The `updater` function argument is fully type-inferred from your OpenAPI schema, so you get autocompletion and type safety for the cached data. No manual type annotations are needed.
2 changes: 1 addition & 1 deletion packages/openapi-react-query/biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"extends": ["../../biome.json"],
"files": {
"ignore": ["./test/fixtures/"]
"ignore": ["./test/fixtures/", "./examples/setquerydata-example/api.d.ts"]
},
"linter": {
"rules": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# setQueryData Example

This example demonstrates the type-safe `setQueryData` functionality in `openapi-react-query`.

## Overview

The `setQueryData` method allows you to update cached query data with full TypeScript type safety. The updater function receives the current cached data and must return data of the same type, ensuring type consistency.

## Setup

1. Generate TypeScript types from the OpenAPI spec:

```bash
npm run generate-types
```

1. Run TypeScript check to see type safety in action:

```bash
npm run type-check
```
141 changes: 141 additions & 0 deletions packages/openapi-react-query/examples/setquerydata-example/api.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/

export interface paths {
"/posts": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get all posts */
get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Post"][];
};
};
};
};
put?: never;
/** Create a new post */
post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["PostInput"];
};
};
responses: {
/** @description Post created */
201: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Post"];
};
};
};
};
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/posts/{id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get a single post */
get: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Post"];
};
};
/** @description Post not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Error"];
};
};
};
};
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
Post: {
id: string;
title: string;
content: string;
/** Format: date-time */
createdAt?: string;
};
PostInput: {
title: string;
content: string;
};
Error: {
code: number;
message: string;
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export type operations = Record<string, never>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Example demonstrating setQueryData with type safety
import createFetchClient from "openapi-fetch";
import createClient from "../../src";
// NOTE: If running locally, ensure you import from the local source, not the package name, to avoid module resolution errors.
import { QueryClient } from "@tanstack/react-query";
import type { paths, components } from "./api";

type Post = components["schemas"]["Post"];

// Create clients
const fetchClient = createFetchClient<paths>({
baseUrl: "https://api.example.com",
});
const $api = createClient(fetchClient);
const queryClient = new QueryClient();

// Simulate creating a new post
const newPost: Post = {
id: "123",
title: "New Post",
content: "Post content",
createdAt: new Date().toISOString(),
};

// Example 1: Update posts list with updater function
async function createPostAndUpdateCache() {
$api.setQueryData(
"get",
"/posts",
(oldPosts) => {
return oldPosts ? [...oldPosts, newPost] : [newPost];
},
queryClient,
{},
);

return newPost;
}

// Example 2: Update a single post after editing
async function updatePostAndUpdateCache(postId: string, updates: { title?: string; content?: string }) {
// Update the specific post cache with updater function
// This should now error: missing required init (id)
$api.setQueryData(
"get",
"/posts/{id}",
(oldPost) => {
if (!oldPost) {
throw new Error("No post in cache");
}
return { ...oldPost, ...updates };
},
queryClient,
{ params: { path: { id: postId } } },
);

// This should be fine: /posts does not require init
$api.setQueryData("get", "/posts", (oldPosts) => oldPosts || [], queryClient);
}

// Example 3: Directly set the data
function clearPostsCache() {
$api.setQueryData("get", "/posts", [newPost], queryClient, {});
}

export { createPostAndUpdateCache, updatePostAndUpdateCache, clearPostsCache };
Loading
Loading