From 072543fc1723b81eae2d99639d7c36bd8db94126 Mon Sep 17 00:00:00 2001 From: Scott Prue Date: Thu, 18 Dec 2025 12:22:12 -0700 Subject: [PATCH 1/4] feat(swr-openapi): useSWRMutation wrapper #2367 --- packages/swr-openapi/src/index.ts | 1 + packages/swr-openapi/src/mutation.ts | 64 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 packages/swr-openapi/src/mutation.ts diff --git a/packages/swr-openapi/src/index.ts b/packages/swr-openapi/src/index.ts index 5f9b3300d..3ef60306c 100644 --- a/packages/swr-openapi/src/index.ts +++ b/packages/swr-openapi/src/index.ts @@ -3,3 +3,4 @@ export * from "./infinite.js"; export * from "./mutate.js"; export * from "./query.js"; export * from "./types.js"; +export * from "./mutation.js"; diff --git a/packages/swr-openapi/src/mutation.ts b/packages/swr-openapi/src/mutation.ts new file mode 100644 index 000000000..6e5a9a944 --- /dev/null +++ b/packages/swr-openapi/src/mutation.ts @@ -0,0 +1,64 @@ +import type { Client } from "openapi-fetch"; +import type { HttpMethod, MediaType, PathsWithMethod } from "openapi-typescript-helpers"; +import useSWRMutation, { type SWRMutationConfiguration, type SWRMutationResponse } from "swr/mutation"; +import type { TypesForRequest } from "./types.js"; + +/** + * Produces a typed wrapper for [`useSWRMutation`](https://swr.vercel.app/docs/mutation). + * + * ```ts + * import createClient from "openapi-fetch"; + * import type { paths } from "./my-openapi-3-schema"; // generated types + * + * const client = createClient({ baseUrl: "https://my-api.com" }); + * const useMutation = createMutationHook(client, "my-api"); + * + * function MyComponent() { + * const { trigger, data, isMutating } = useMutation("/users", "post"); + * + * return ( + * + * ); + * } + * ``` + */ +export function createMutationHook( + client: Client, + prefix: string, +) { + return function useMutation< + Method extends Extract, + Path extends PathsWithMethod, + T extends TypesForRequest = TypesForRequest, + Data = T["Data"], + Error = T["Error"], + Init = T["Init"], + >( + path: Path, + method: Method, + config?: SWRMutationConfiguration, + ): SWRMutationResponse { + const key = [prefix, path, method] as const; + + return useSWRMutation( + key, + async (_key, { arg }) => { + const m = method.toUpperCase() as Uppercase; + + const res = await (client as any)[m](path, arg); + if (res.error) { + throw res.error; + } + return res.data; + }, + config, + ); + }; +} From a4d801ab8b2473e1f65eeaf436bdda73f56a98c1 Mon Sep 17 00:00:00 2001 From: Scott Prue Date: Thu, 18 Dec 2025 14:12:44 -0700 Subject: [PATCH 2/4] chore: add docs --- docs/swr-openapi/use-mutation.md | 82 +++++++++++++++++++++++++++++++ packages/swr-openapi/src/index.ts | 2 +- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 docs/swr-openapi/use-mutation.md diff --git a/docs/swr-openapi/use-mutation.md b/docs/swr-openapi/use-mutation.md new file mode 100644 index 000000000..714e63455 --- /dev/null +++ b/docs/swr-openapi/use-mutation.md @@ -0,0 +1,82 @@ +--- +title: useMutation +--- + +# {{ $frontmatter.title }} + +`useMutation` is a wrapper around SWR's [useSWRMutation][swr-use-mutation] function. It provides a type-safe hook for remote mutations. + +```tsx +import createClient from "openapi-fetch"; +import type { paths } from "./my-openapi-3-schema"; // generated types + +const client = createClient({ baseUrl: "https://my-api.com" }); +const useMutation = createMutationHook(client, "my-api"); + +function MyComponent() { + const { trigger, data, isMutating } = useMutation("/users", "post"); + + return ( + + ); +} +``` + +## API + +### Parameters + +- `key`: + - `path`: Any endpoint that supports `GET` requests. + - `init`: (_optional_) Partial fetch options for the chosen endpoint. +- `method`: HTTP method for the chosen endpoint. +- `options`: (_optional_) [SWR mutate options][swr-use-mutation-params]. + +### Returns + +- Return of a [useSWRMutation][swr-mutation-response] including: + +`data`: data for the given key returned from fetcher +`error`: error thrown by fetcher (or undefined) +`trigger(arg, options)`: a function to trigger a remote mutation +`reset`: a function to reset the state (data, error, isMutating) +`isMutating`: if there's an ongoing remote mutation + +## How It Works + +```ts +function useMutation( + path, + method, + config, +) { + const key = [prefix, path, method]; + + return useSWRMutation( + key, + async (_key, { arg }) => { + const m = method.toUpperCase(); + + const res = await client[m](path, arg); + if (res.error) { + throw res.error; + } + return res.data; + }, + config, + ); +}; + +``` + +[swr-mutate-params]: https://swr.vercel.app/docs/mutation#parameters +[swr-use-mutation]: https://swr.vercel.app/docs/mutation#useswrmutation +[swr-use-mutation-params]: https://swr.vercel.app/docs/mutation#useswrmutation-parameters +[swr-mutation-response]: https://swr.vercel.app/docs/mutation#useswrmutation-return-values diff --git a/packages/swr-openapi/src/index.ts b/packages/swr-openapi/src/index.ts index 3ef60306c..942730171 100644 --- a/packages/swr-openapi/src/index.ts +++ b/packages/swr-openapi/src/index.ts @@ -1,6 +1,6 @@ export * from "./immutable.js"; export * from "./infinite.js"; export * from "./mutate.js"; +export * from "./mutation.js"; export * from "./query.js"; export * from "./types.js"; -export * from "./mutation.js"; From 8762884d9dbb9e095993f39aa64e793e313a0a9e Mon Sep 17 00:00:00 2001 From: Scott Prue Date: Thu, 18 Dec 2025 14:18:23 -0700 Subject: [PATCH 3/4] chore: add to changelog --- packages/swr-openapi/CHANGELOG.md | 6 ++++++ packages/swr-openapi/package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/swr-openapi/CHANGELOG.md b/packages/swr-openapi/CHANGELOG.md index fd4467996..96d9e8d1c 100644 --- a/packages/swr-openapi/CHANGELOG.md +++ b/packages/swr-openapi/CHANGELOG.md @@ -1,5 +1,11 @@ # swr-openapi +## 5.5.0 + +### Minor Changes + +- [#2552](https://github.com/openapi-ts/openapi-typescript/pull/2552) [`072543f`](https://github.com/openapi-ts/openapi-typescript/pull/2552/commits/072543fc1723b81eae2d99639d7c36bd8db94126) Thanks [@prescottprue](https://github.com/prescottprue)! - Add useMutation hook + ## 5.4.2 ### Patch Changes diff --git a/packages/swr-openapi/package.json b/packages/swr-openapi/package.json index 9813d2f02..36c7bb768 100644 --- a/packages/swr-openapi/package.json +++ b/packages/swr-openapi/package.json @@ -1,7 +1,7 @@ { "name": "swr-openapi", "description": "Generate SWR hooks from OpenAPI schemas", - "version": "5.4.2", + "version": "5.5.0", "author": { "name": "Hunter Tunnicliff", "email": "hunter@tunnicliff.co" From b2f2eadc2c9c2bcbe4d530a201adc124369c647d Mon Sep 17 00:00:00 2001 From: Scott Prue Date: Thu, 18 Dec 2025 14:35:31 -0700 Subject: [PATCH 4/4] fix: add init --- docs/swr-openapi/use-mutation.md | 10 +++++++--- packages/swr-openapi/src/mutation.ts | 8 +++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/swr-openapi/use-mutation.md b/docs/swr-openapi/use-mutation.md index 714e63455..495fee571 100644 --- a/docs/swr-openapi/use-mutation.md +++ b/docs/swr-openapi/use-mutation.md @@ -14,16 +14,20 @@ const client = createClient({ baseUrl: "https://my-api.com" }); const useMutation = createMutationHook(client, "my-api"); function MyComponent() { - const { trigger, data, isMutating } = useMutation("/users", "post"); + const { trigger, data, isMutating } = useMutation("/users/{userId}", "post", { + params: { + userId: "123", + }, + }); return ( ); } diff --git a/packages/swr-openapi/src/mutation.ts b/packages/swr-openapi/src/mutation.ts index 6e5a9a944..11c45148e 100644 --- a/packages/swr-openapi/src/mutation.ts +++ b/packages/swr-openapi/src/mutation.ts @@ -2,6 +2,7 @@ import type { Client } from "openapi-fetch"; import type { HttpMethod, MediaType, PathsWithMethod } from "openapi-typescript-helpers"; import useSWRMutation, { type SWRMutationConfiguration, type SWRMutationResponse } from "swr/mutation"; import type { TypesForRequest } from "./types.js"; +import { useMemo } from "react"; /** * Produces a typed wrapper for [`useSWRMutation`](https://swr.vercel.app/docs/mutation). @@ -43,9 +44,10 @@ export function createMutationHook( path: Path, method: Method, - config?: SWRMutationConfiguration, - ): SWRMutationResponse { - const key = [prefix, path, method] as const; + init: Init | null, + config?: SWRMutationConfiguration, + ): SWRMutationResponse { + const key = useMemo(() => (init !== null ? ([prefix, path, init] as const) : null), [prefix, path, init]); return useSWRMutation( key,