Skip to content

Commit 8bcd96c

Browse files
committed
elaborate comments
1 parent 693d39c commit 8bcd96c

File tree

1 file changed

+37
-11
lines changed

1 file changed

+37
-11
lines changed

libs/api/hooks/use-api-data.ts

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,59 @@ export const sortObj = (obj: Params): Params => {
1111
return sorted
1212
}
1313

14+
// https://github.com/piotrwitek/utility-types/tree/df2502e#pickbyvaluet-valuetype
1415
type PickByValue<T, ValueType> = Pick<
1516
T,
1617
{ [Key in keyof T]-?: T[Key] extends ValueType ? Key : never }[keyof T]
1718
>
1819

1920
/* eslint-disable @typescript-eslint/no-explicit-any */
21+
22+
// given an API object A and a key K where A[K] is a function that takes a
23+
// single argument and returns a promise...
24+
25+
// extract the type of the argument (if it extends Params)
2026
type ReqParams<A, K extends keyof A> = A[K] extends (p: infer P) => Promise<any>
2127
? P extends Params
2228
? P
2329
: never
2430
: never
2531

32+
// extract the type of the value inside the promise
2633
type Response<A, K extends keyof A> = A[K] extends (p: any) => Promise<infer R>
2734
? R
2835
: never
2936

30-
// The first argument to useSWR in the standard use case would be
31-
// the URL to fetch. It is used to uniquely identify the request
32-
// for caching purposes. If multiple components request the same
33-
// thing at the same time, SWR will only make one HTTP request.
34-
// Because we have a generated client library, we do not have URLs.
35-
// Instead, we have function names and parameter objects. But object
36-
// literals do not have referential stability across renders, so we
37-
// have to use JSON.stringify to turn the params into a stable key.
38-
// We also sort the keys in the params object so that { a: 1, b: 2 }
39-
// and { b: 2, a: 1 } are considered equivalent. SWR accepts an array
40-
// of strings as well as a single string.
37+
// This all needs explanation. The easiest starting point is what this would
38+
// look like in plain JS, which is quite simple:
39+
//
40+
// const getUseApi = (api) => (method, params) => {
41+
// const paramsStr = JSON.stringify(sortObj(params))
42+
// return useSWR([method, paramsStr], () => api[method](params))
43+
// }
44+
//
45+
// 1. what's up with the JSON.stringify/
46+
//
47+
// The first argument to useSWR in the standard use case would be the URL to
48+
// fetch. It is used to uniquely identify the request for caching purposes. If
49+
// multiple components request the same thing at the same time, SWR will only
50+
// make one HTTP request. Because we have a generated client library, we do not
51+
// have URLs. Instead, we have function names and parameter objects. But object
52+
// literals do not have referential stability across renders, so we have to use
53+
// JSON.stringify to turn the params into a stable key. We also sort the keys in
54+
// the params object so that { a: 1, b: 2 } and { b: 2, a: 1 } are considered
55+
// equivalent. SWR accepts an array of strings as well as a single string.
56+
//
57+
// 2. what's up with the types?
58+
//
59+
// The type situation here is pretty gnarly considering how simple the plain JS
60+
// version is. The difficulty is that we want full type safety, i.e., based on
61+
// the method name passed in, we want the typechecker to check the params and
62+
// annotate the response. PickByValue ensures we only call methods on the API
63+
// object that follow the (params) => Promise<Response> pattern. Then we use the
64+
// inferred type of the key (the method name) to enforce that params have the
65+
// actual type of the params on the named method. Finally we use the Response
66+
// helper to tell useSWR what type to put on the response data.
4167
export function getUseApi<A extends PickByValue<A, (p: any) => Promise<any>>>(
4268
api: A
4369
) {

0 commit comments

Comments
 (0)