swr-openapi v5.0.0
Installation
npm install swr-openapi swr openapi-fetch
Setup
Follow openapi-typescript directions to generate TypeScript definitions for each service being used.
Here is an example of types being generated for a service via the command line:
npx openapi-typescript "https://sandwiches.example/openapi/json" --output ./types/sandwich-schema.ts
Initialize an openapi-fetch client and create any desired hooks.
// sandwich-api.ts
import createClient from "openapi-fetch";
import { createQueryHook } from "swr-openapi";
import type { paths as SandwichPaths } from "./types/sandwich-schema";
const client = createClient<SandwichPaths>(/* ... */);
const useSandwiches = createQueryHook(client, "sandwich-api");
const { data, error, isLoading, isValidating, mutate } = useSandwiches(
"/sandwich/{id}", // <- Fully typed paths!
{
params: {
path: {
id: "123", // <- Fully typed params!
},
},
},
);
Wrapper hooks are provided 1:1 for each hook exported by SWR.
API Reference
Hook Builders
import createClient from "openapi-fetch";
import {
createQueryHook,
createImmutableHook,
createInfiniteHook,
createMutateHook,
} from "swr-openapi";
import { paths as SomeApiPaths } from "./some-api";
const client = createClient<SomeApiPaths>(/* ... */);
const prefix = "some-api";
export const useQuery = createQueryHook(client, prefix);
export const useImmutable = createImmutableHook(client, prefix);
export const useInfinite = createInfiniteHook(client, prefix);
export const useMutate = createMutateHook(
client,
prefix,
_.isMatch, // Or any comparision function
);
Parameters
Each builder hook accepts the same initial parameters:
client
: An OpenAPI fetch client.prefix
: A prefix unique to the fetch client.
createMutateHook
also accepts a third parameter:
compare
: A function to compare fetch options).
Returns
createQueryHook
→useQuery
createImmutableHook
→useImmutable
createInfiniteHook
→useInfinite
createMutateHook
→useMutate
useQuery
This hook is a typed wrapper over useSWR
.
const useQuery = createQueryHook(/* ... */);
const { data, error, isLoading, isValidating, mutate } = useQuery(
path,
init,
config,
);
useQuery
is a very thin wrapper over useSWR
. Most of the code involves TypeScript generics that are transpiled away.
The prefix supplied in createQueryHook
is joined with path
and init
to form the key passed to SWR.
prefix
is only used to help ensure uniqueness for SWR's cache, in the case that two or more API clients share an identical path (e.g./api/health
). It is not included in actualGET
requests.
Then, GET
is invoked with path
and init
. Short and sweet.
function useQuery(path, ...[init, config]) {
return useSWR(
init !== null ? [prefix, path, init] : null,
async ([_prefix, path, init]) => {
const res = await client.GET(path, init);
if (res.error) {
throw res.error;
}
return res.data;
},
config,
);
}
Parameters
path
: Any endpoint that supportsGET
requests.init
: (sometimes optional^1)- Fetch options for the chosen endpoint.
null
to skip the request (see SWR Conditional Fetching).
config
: (optional) SWR options.
^1: When an endpoint has required params, init
will be required, otherwise init
will be optional.
Returns
- An SWR response.
useImmutable
This hook has the same contracts as useQuery
. However, instead of wrapping useSWR
, it wraps useSWRImmutable
. This immutable hook disables automatic revalidations but is otherwise identical to useSWR
.
const useImmutable = createImmutableHook(/* ... */);
const { data, error, isLoading, isValidating, mutate } = useImmutable(
path,
init,
config,
);
Parameters
Identical to useQuery
parameters.
Returns
Identical to useQuery
returns.
useInfinite
This hook is a typed wrapper over useSWRInfinite
.
const useInfinite = createInfiniteHook(/* ... */);
const { data, error, isLoading, isValidating, mutate, size, setSize } =
useInfinite(path, getInit, config);
Just as useQuery
is a thin wrapper over useSWR
, useInfinite
is a thin wrapper over useSWRInfinite
.
Instead of using static fetch options as part of the SWR key, useInfinite
is given a function (getInit
) that should dynamically determines the fetch options based on the current page index and the data from a previous page.
function useInfinite(path, getInit, config) {
const fetcher = async ([_, path, init]) => {
const res = await client.GET(path, init);
if (res.error) {
throw res.error;
}
return res.data;
};
const getKey = (index, previousPageData) => {
const init = getInit(index, previousPageData);
if (init === null) {
return null;
}
const key = [prefix, path, init];
return key;
};
return useSWRInfinite(getKey, fetcher, config);
}
Parameters
path
: Any endpoint that supportsGET
requests.getInit
: A function that returns the fetch options for a given page (learn more).config
: (optional) SWR infinite options.
Returns
getInit
This function is similar to the getKey
parameter accepted by useSWRInfinite
, with some slight alterations to take advantage of Open API types.
Parameters
pageIndex
: The zero-based index of the current page to load.previousPageData
:undefined
(if on the first page).- The fetched response for the last page retrieved.
Returns
- Fetch options for the next page to load.
null
if no more pages should be loaded.
Examples
useInfinite("/something", (pageIndex, previousPageData) => {
// No more pages
if (previousPageData && !previousPageData.hasMore) {
return null;
}
// First page
if (!previousPageData) {
return {
params: {
query: {
limit: 10,
},
},
};
}
// Next page
return {
params: {
query: {
limit: 10,
offset: 10 * pageIndex,
},
},
};
});
useInfinite("/something", (pageIndex, previousPageData) => {
// No more pages
if (previousPageData && !previousPageData.nextCursor) {
return null;
}
// First page
if (!previousPageData) {
return {
params: {
query: {
limit: 10,
},
},
};
}
// Next page
return {
params: {
query: {
limit: 10,
cursor: previousPageData.nextCursor,
},
},
};
});
useMutate
useMutate
is a wrapper around SWR's global mutate function. It provides a type-safe mechanism for updating and revalidating SWR's client-side cache for specific endpoints.
Like global mutate, this mutate wrapper accepts three parameters: key
, data
, and options
. The latter two parameters are identical to those in bound mutate. key
can be either a path alone, or a path with fetch options.
The level of specificity used when defining the key will determine which cached requests are updated. If only a path is provided, any cached request using that path will be updated. If fetch options are included in the key, the compare
function will determine if a cached request's fetch options match the key's fetch options.
const mutate = useMutate();
await mutate([path, init], data, options);
function useMutate() {
const { mutate } = useSWRConfig();
return useCallback(
([path, init], data, opts) => {
return mutate(
(key) => {
if (!Array.isArray(key) || ![2, 3].includes(key.length)) {
return false;
}
const [keyPrefix, keyPath, keyOptions] = key;
return (
keyPrefix === prefix &&
keyPath === path &&
(init ? compare(keyOptions, init) : true)
);
},
data,
opts,
);
},
[mutate, prefix, compare],
);
}
Parameters
key
:path
: Any endpoint that supportsGET
requests.init
: (optional) Partial fetch options for the chosen endpoint.
data
: (optional)- Data to update the client cache.
- An async function for a remote mutation.
options
: (optional) SWR mutate options.
Returns
- A promise containing an array, where each array item is either updated data for a matched key or
undefined
.
SWR's
mutate
signature specifies that when a matcher function is used, the return type will be an array. Since our wrapper uses a key matcher function, it will always return an array type.
compare
When calling createMutateHook
, a function must be provided with the following contract:
type Compare = (init: any, partialInit: object) => boolean;
This function is used to determine whether or not a cached request should be updated when mutate
is called with fetch options.
My personal recommendation is to use lodash's isMatch
:
Performs a partial deep comparison between object and source to determine if object contains equivalent property values.
const useMutate = createMutateHook(client, "<unique-key>", isMatch);
const mutate = useMutate();
await mutate([
"/path",
{
params: {
query: {
version: "beta",
},
},
},
]);
// ✅ Would be updated
useQuery("/path", {
params: {
query: {
version: "beta",
},
},
});
// ✅ Would be updated
useQuery("/path", {
params: {
query: {
version: "beta",
other: true,
example: [1, 2, 3],
},
},
});
// ❌ Would not be updated
useQuery("/path", {
params: {
query: {},
},
});
// ❌ Would not be updated
useQuery("/path");
// ❌ Would not be updated
useQuery("/path", {
params: {
query: {
version: "alpha",
},
},
});
// ❌ Would not be updated
useQuery("/path", {
params: {
query: {
different: "items",
},
},
});
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
10 months ago
10 months ago
12 months ago
10 months ago
10 months ago
10 months ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago