Skip to content

Commit 2ee8bcd

Browse files
committed
Refactor primary hook factory
1 parent fbd8460 commit 2ee8bcd

File tree

2 files changed

+92
-124
lines changed

2 files changed

+92
-124
lines changed

src/index.ts

Lines changed: 70 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import type createClient from "openapi-fetch";
2-
import type {
3-
FetchOptions,
4-
FetchResponse,
5-
ParseAsResponse,
6-
} from "openapi-fetch";
2+
import type { FetchOptions, ParseAsResponse } from "openapi-fetch";
73
import type {
84
ErrorResponse,
95
FilterKeys,
@@ -12,108 +8,89 @@ import type {
128
ResponseObjectMap,
139
SuccessResponse,
1410
} from "openapi-typescript-helpers";
15-
import useSWR, { Fetcher, type SWRConfiguration } from "swr";
16-
import { SWRInfiniteKeyLoader } from "swr/infinite";
11+
import useSWR, { type SWRConfiguration } from "swr";
12+
import useSWRInfinite, { SWRInfiniteKeyLoader } from "swr/infinite";
1713

18-
export function makeHookFactory<Paths extends {}>(
14+
export function createHooks<Paths extends {}>(
1915
api: ReturnType<typeof createClient<Paths>>,
2016
keyPrefix: string,
2117
) {
22-
// Define hook factory with typed paths
23-
return function hookFactory<
18+
function use<
2419
Path extends PathsWithMethod<Paths, "get">,
2520
Req extends FilterKeys<Paths[Path], "get">,
26-
>(path: Path, swrConfigDefaults?: SWRConfiguration) {
27-
// Define hook that is returned for consumers with typed options
28-
// based on the given path
29-
function useHook<
30-
Options extends FetchOptions<Req>,
31-
Data extends ParseAsResponse<
32-
FilterKeys<SuccessResponse<ResponseObjectMap<Req>>, MediaType>,
33-
Options
34-
>,
35-
Error extends FilterKeys<
36-
ErrorResponse<ResponseObjectMap<Req>>,
37-
MediaType
38-
>,
39-
Config extends SWRConfiguration<Data, Error>,
40-
>(fetchOptions: Options | null, swrConfig?: Config) {
41-
type Key = [typeof keyPrefix, Path, Options] | null;
42-
43-
return useSWR<Data, Error, Key, Config>(
44-
// SWR key is based on the path and fetch options
45-
// keyPrefix keeps each API's cache separate in case there are path collisions
46-
fetchOptions ? [keyPrefix, path, fetchOptions] : null,
47-
// Fetcher function
48-
// @ts-expect-error - This functions correctly, but we don't need to fix its types
49-
// types since we rely on the generics passed to useSWR instead
50-
async ([_, url, options]) => {
51-
const res = await api.GET(url, options);
52-
if (res.error) {
53-
throw res.error;
54-
}
55-
return res.data;
56-
},
57-
// SWR config
58-
{
59-
...swrConfigDefaults,
60-
...swrConfig,
61-
},
62-
);
63-
}
64-
65-
useHook.getKey = (fetchOptions: FetchOptions<Req>) =>
66-
[keyPrefix, path, fetchOptions] as [
67-
typeof keyPrefix,
68-
Path,
69-
typeof fetchOptions,
70-
];
21+
Options extends FetchOptions<Req>,
22+
Data extends ParseAsResponse<
23+
FilterKeys<SuccessResponse<ResponseObjectMap<Req>>, MediaType>,
24+
Options
25+
>,
26+
Error extends FilterKeys<ErrorResponse<ResponseObjectMap<Req>>, MediaType>,
27+
Config extends SWRConfiguration<Data, Error>,
28+
>(path: Path, options: Options | null, swrConfig?: Config) {
29+
type Key = [typeof keyPrefix, Path, Options] | null;
7130

72-
return useHook;
73-
};
74-
}
31+
return useSWR<Data, Error, Key, Config>(
32+
// SWR key is based on the path and fetch options
33+
// keyPrefix keeps each API's cache separate in case there are path collisions
34+
options ? [keyPrefix, path, options] : null,
35+
// Fetcher function
36+
// @ts-expect-error - This functions correctly, but we don't need to fix its types
37+
// types since we rely on the generics passed to useSWR instead
38+
async ([_, url, init]) => {
39+
const res = await api.GET(url, init);
40+
if (res.error) {
41+
throw res.error;
42+
}
43+
return res.data;
44+
},
45+
// SWR config
46+
swrConfig,
47+
);
48+
}
7549

76-
export function makeSWRHelper<Paths extends {}>(
77-
api: ReturnType<typeof createClient<Paths>>,
78-
keyPrefix: string,
79-
) {
80-
function swrHelper<
50+
function useInfinite<
8151
Path extends PathsWithMethod<Paths, "get">,
8252
Req extends FilterKeys<Paths[Path], "get">,
8353
Options extends FetchOptions<Req>,
84-
Data extends NonNullable<FetchResponse<Req, Options>["data"]>,
85-
Key extends [string, Path, Options],
86-
OptionsInput extends
87-
| Options
88-
| ((index: number, previousPageData: Data | null) => Options | null),
54+
Data extends ParseAsResponse<
55+
FilterKeys<SuccessResponse<ResponseObjectMap<Req>>, MediaType>,
56+
Options
57+
>,
58+
Error extends FilterKeys<ErrorResponse<ResponseObjectMap<Req>>, MediaType>,
59+
Config extends SWRConfiguration<Data, Error>,
8960
>(
9061
path: Path,
91-
optionsOrFn: OptionsInput,
92-
): OptionsInput extends Options
93-
? [Key, Fetcher<Data, Key>]
94-
: [SWRInfiniteKeyLoader<Data, Key>, Fetcher<Data, Key>] {
95-
const key =
96-
typeof optionsOrFn === "function"
97-
? (index: number, previousPageData: Data | null) => {
98-
const options = optionsOrFn(index, previousPageData);
99-
if (options === null) {
100-
return null;
101-
}
102-
return [keyPrefix, path, options] as const;
103-
}
104-
: ([keyPrefix, path, optionsOrFn] as const);
105-
106-
const fetcher = async ([, url, init]: Key) => {
107-
const { data, error } = await api.GET(url, init);
108-
if (error) {
109-
throw error;
110-
}
111-
return data!;
112-
};
62+
getOptionsFn: SWRInfiniteKeyLoader<Data, Options | null>,
63+
swrConfig?: Config,
64+
) {
65+
type Key = [typeof keyPrefix, Path, Options] | null;
11366

114-
// @ts-expect-error - TODO: Come back to make the types sound
115-
return [key, fetcher] as const;
67+
return useSWRInfinite<Data, Error, SWRInfiniteKeyLoader<Data, Key>>(
68+
// SWR key is based on the path and fetch options
69+
// keyPrefix keeps each API's cache separate in case there are path collisions
70+
(index, previousPageData) => {
71+
const options = getOptionsFn(index, previousPageData);
72+
if (options === null) {
73+
return null;
74+
}
75+
return [keyPrefix, path, options] as const;
76+
},
77+
// Fetcher function
78+
// @ts-expect-error - This functions correctly, but we don't need to fix its types
79+
// types since we rely on the generics passed to useSWR instead
80+
async ([_, url, init]) => {
81+
const res = await api.GET(url, init);
82+
if (res.error) {
83+
throw res.error;
84+
}
85+
return res.data;
86+
},
87+
// SWR config
88+
swrConfig,
89+
);
11690
}
11791

118-
return swrHelper;
92+
return {
93+
use,
94+
useInfinite,
95+
};
11996
}

test/index.spec.ts

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
import { expectTypeOf } from "expect-type";
22
import createClient from "openapi-fetch";
33
import { SuccessResponseJSON } from "openapi-typescript-helpers";
4-
import useSWR from "swr";
5-
import useSWRInfinite from "swr/infinite";
64
import type { paths } from "../generated/petstore";
7-
import { makeHookFactory, makeSWRHelper } from "../src/index";
5+
import { createHooks } from "../src/index";
86

97
const petStoreApi = createClient<paths>({
108
baseUrl: "https://petstore3.swagger.io/api/v3",
119
});
1210

13-
const petStoreHook = makeHookFactory<paths>(petStoreApi, "pet-store");
14-
15-
const useOrder = petStoreHook("/store/order/{orderId}");
11+
const { use: usePetStore, useInfinite: usePetStoreInfinite } =
12+
createHooks<paths>(petStoreApi, "pet-store");
1613

1714
type OrderSuccessResponse = SuccessResponseJSON<
1815
paths["/store/order/{orderId}"]["get"]
1916
>;
2017

2118
// Test regular hook
22-
const { data } = useOrder({
19+
const { data } = usePetStore("/store/order/{orderId}", {
2320
params: {
2421
path: { orderId: 1 },
2522
},
@@ -33,39 +30,33 @@ if (data) {
3330
}
3431

3532
// Test suspense hook
36-
const { data: suspenseData } = useOrder(
33+
const { data: suspenseData } = usePetStore(
34+
"/store/order/{orderId}",
3735
{
3836
params: {
39-
path: { orderId: 1 },
37+
path: {
38+
orderId: 1,
39+
},
4040
},
4141
},
4242
{ suspense: true },
4343
);
4444

4545
expectTypeOf(suspenseData).toEqualTypeOf<OrderSuccessResponse>();
4646

47-
const swrPetStore = makeSWRHelper(petStoreApi, "pet-store");
48-
49-
swrPetStore("/store/order/{orderId}", {
50-
params: { path: { orderId: 1 } },
47+
usePetStore("/store/order/{orderId}", {
48+
params: {
49+
path: {
50+
orderId: 1,
51+
},
52+
},
5153
});
5254

53-
swrPetStore("/store/order/{orderId}", () => ({
54-
params: { path: { orderId: 1 } },
55-
}));
56-
57-
useSWR(
58-
...swrPetStore("/store/order/{orderId}", {
59-
params: { path: { orderId: 1 } },
60-
}),
61-
);
62-
63-
useSWRInfinite(
64-
...swrPetStore("/pet/findByStatus", (index, previous) => ({
65-
params: {
66-
query: {
67-
status: "available",
68-
},
55+
usePetStoreInfinite("/store/order/{orderId}", (index, previous) => ({
56+
params: {
57+
path: {
58+
orderId: 1,
6959
},
70-
})),
71-
);
60+
query: {},
61+
},
62+
}));

0 commit comments

Comments
 (0)