@@ -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
1415type 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)
2026type 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
2633type 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 match the
65+ // expected params on the named method. Finally we use the Response helper to
66+ // tell useSWR what type to put on the response data.
4167export function getUseApi < A extends PickByValue < A , ( p : any ) => Promise < any > > > (
4268 api : A
4369) {
0 commit comments