-
Notifications
You must be signed in to change notification settings - Fork 18
Cleaner useApi hook, remove method name hack #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,36 +1 @@ | ||
| import useSWR from 'swr' | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| type Params = Record<string, any> | ||
|
|
||
| // TODO: write tests for this | ||
| export const sortObj = (obj: Params): Params => { | ||
| const sorted: Params = {} | ||
| for (const k of Object.keys(obj).sort()) { | ||
| sorted[k] = obj[k] | ||
| } | ||
| return sorted | ||
| } | ||
|
|
||
| // The first argument to useSWR in the standard use case would be | ||
| // the URL to fetch. It is used to uniquely identify the request | ||
| // for caching purposes. If multiple components request the same | ||
| // thing at the same time, SWR will only make one HTTP request. | ||
| // Because we have a generated client library, we do not have URLs. | ||
| // Instead, we have function names and parameter objects. But object | ||
| // literals do not have referential stability across renders, so we | ||
| // have to use JSON.stringify to turn the params into a stable key. | ||
| // We also sort the keys in the params object so that { a: 1, b: 2 } | ||
| // and { b: 2, a: 1 } are considered equivalent. SWR accepts an array | ||
| // of strings as well as a single string. | ||
| export function useApiData<P extends Params, R>( | ||
| method: (p: P) => Promise<R>, | ||
| params: P | ||
| ) { | ||
| if (process.env.NODE_ENV === 'development' && method.name === '') { | ||
| throw new Error('API method must have a name') | ||
| } | ||
|
|
||
| const paramsStr = JSON.stringify(sortObj(params)) | ||
| return useSWR<R>([method.name, paramsStr], () => method(params)) | ||
| } | ||
| export { getUseApi } from './use-api-data' |
2 changes: 1 addition & 1 deletion
2
libs/api/hooks/index.spec.ts → libs/api/hooks/use-api-data.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import useSWR from 'swr' | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| type Params = Record<string, any> | ||
|
|
||
| export const sortObj = (obj: Params): Params => { | ||
| const sorted: Params = {} | ||
| for (const k of Object.keys(obj).sort()) { | ||
| sorted[k] = obj[k] | ||
| } | ||
| return sorted | ||
| } | ||
|
|
||
| // https://github.com/piotrwitek/utility-types/tree/df2502e#pickbyvaluet-valuetype | ||
| type PickByValue<T, ValueType> = Pick< | ||
| T, | ||
| { [Key in keyof T]-?: T[Key] extends ValueType ? Key : never }[keyof T] | ||
| > | ||
|
|
||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
|
|
||
| // given an API object A and a key K where A[K] is a function that takes a | ||
| // single argument and returns a promise... | ||
|
|
||
| // extract the type of the argument (if it extends Params) | ||
| type ReqParams<A, K extends keyof A> = A[K] extends (p: infer P) => Promise<any> | ||
| ? P extends Params | ||
| ? P | ||
| : never | ||
| : never | ||
|
|
||
| // extract the type of the value inside the promise | ||
| type Response<A, K extends keyof A> = A[K] extends (p: any) => Promise<infer R> | ||
| ? R | ||
| : never | ||
|
|
||
| // This all needs explanation. The easiest starting point is what this would | ||
| // look like in plain JS, which is quite simple: | ||
| // | ||
| // const getUseApi = (api) => (method, params) => { | ||
| // const paramsStr = JSON.stringify(sortObj(params)) | ||
| // return useSWR([method, paramsStr], () => api[method](params)) | ||
| // } | ||
| // | ||
| // 1. what's up with the JSON.stringify/ | ||
| // | ||
| // The first argument to useSWR in the standard use case would be the URL to | ||
| // fetch. It is used to uniquely identify the request for caching purposes. If | ||
| // multiple components request the same thing at the same time, SWR will only | ||
| // make one HTTP request. Because we have a generated client library, we do not | ||
| // have URLs. Instead, we have function names and parameter objects. But object | ||
| // literals do not have referential stability across renders, so we have to use | ||
| // JSON.stringify to turn the params into a stable key. We also sort the keys in | ||
| // the params object so that { a: 1, b: 2 } and { b: 2, a: 1 } are considered | ||
| // equivalent. SWR accepts an array of strings as well as a single string. | ||
| // | ||
| // 2. what's up with the types? | ||
| // | ||
| // The type situation here is pretty gnarly considering how simple the plain JS | ||
| // version is. The difficulty is that we want full type safety, i.e., based on | ||
| // the method name passed in, we want the typechecker to check the params and | ||
| // annotate the response. PickByValue ensures we only call methods on the API | ||
| // object that follow the (params) => Promise<Response> pattern. Then we use the | ||
| // inferred type of the key (the method name) to enforce that params match the | ||
| // expected params on the named method. Finally we use the Response helper to | ||
| // tell useSWR what type to put on the response data. | ||
| export function getUseApi<A extends PickByValue<A, (p: any) => Promise<any>>>( | ||
| api: A | ||
| ) { | ||
| function useApi<K extends keyof A>(method: K, params: ReqParams<A, K>) { | ||
| const paramsStr = JSON.stringify(sortObj(params)) | ||
| return useSWR<Response<A, K>>([method, paramsStr], () => | ||
| api[method](params) | ||
| ) | ||
| } | ||
| return useApi | ||
| } | ||
|
|
||
| /* eslint-enable @typescript-eslint/no-explicit-any */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good riddance. blech