Skip to content

Commit be6958c

Browse files
authored
Type useSWR fetcher (#1477)
* type useSWR fetcher add comments about implementation add type test scripts for other middleware * fix yarn.lock * add type check for middleware and fetcher * apply review change and type useSWRInfinite * simplify the implementation of fetcher type Author: YixuanXu <[email protected]>
1 parent fec8e65 commit be6958c

13 files changed

+449
-103
lines changed

immutable/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import useSWR, { Middleware, SWRHook } from 'swr'
1+
import useSWR, { Middleware } from 'swr'
22
import { withMiddleware } from '../src/utils/with-middleware'
33

44
export const immutable: Middleware = useSWRNext => (key, fetcher, config) => {
@@ -9,4 +9,4 @@ export const immutable: Middleware = useSWRNext => (key, fetcher, config) => {
99
return useSWRNext(key, fetcher, config)
1010
}
1111

12-
export default withMiddleware(useSWR as SWRHook, immutable)
12+
export default withMiddleware(useSWR, immutable)

infinite/index.ts

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import useSWR, {
88
Fetcher,
99
SWRHook,
1010
MutatorCallback,
11-
Middleware
11+
Middleware,
12+
ValueKey,
13+
Result
1214
} from 'swr'
1315
import { useIsomorphicLayoutEffect } from '../src/utils/env'
1416
import { serialize } from '../src/utils/serialize'
@@ -18,18 +20,20 @@ import { SWRInfiniteConfiguration, SWRInfiniteResponse } from './types'
1820

1921
const INFINITE_PREFIX = '$inf$'
2022

21-
const getFirstPageKey = (getKey: KeyLoader<any>) => {
23+
const getFirstPageKey = (getKey: KeyLoader) => {
2224
return serialize(getKey ? getKey(0, null) : null)[0]
2325
}
2426

25-
export const unstable_serialize = (getKey: KeyLoader<any>) => {
27+
export const unstable_serialize = (getKey: KeyLoader) => {
2628
return INFINITE_PREFIX + getFirstPageKey(getKey)
2729
}
2830

29-
export const infinite = ((<Data, Error>(useSWRNext: SWRHook) => (
30-
getKey: KeyLoader<Data>,
31+
export const infinite = ((<Data, Error, Args extends ValueKey>(
32+
useSWRNext: SWRHook
33+
) => (
34+
getKey: KeyLoader<Args>,
3135
fn: Fetcher<Data> | null,
32-
config: typeof SWRConfig.default & SWRInfiniteConfiguration<Data, Error>
36+
config: typeof SWRConfig.default & SWRInfiniteConfiguration<Data, Error, Args>
3337
): SWRInfiniteResponse<Data, Error> => {
3438
const rerender = useState({})[1]
3539
const didMountRef = useRef<boolean>(false)
@@ -246,20 +250,37 @@ export const infinite = ((<Data, Error>(useSWRNext: SWRHook) => (
246250
} as SWRInfiniteResponse<Data, Error>
247251
}) as unknown) as Middleware
248252

249-
type SWRInfiniteHook = <Data = any, Error = any>(
250-
...args:
251-
| readonly [KeyLoader<Data>]
252-
| readonly [KeyLoader<Data>, Fetcher<Data> | null]
253-
| readonly [
254-
KeyLoader<Data>,
255-
SWRInfiniteConfiguration<Data, Error> | undefined
256-
]
257-
| readonly [
258-
KeyLoader<Data>,
259-
Fetcher<Data> | null,
260-
SWRInfiniteConfiguration<Data, Error> | undefined
261-
]
262-
) => SWRInfiniteResponse<Data, Error>
253+
export type InfiniteFetcher<
254+
Args extends ValueKey = ValueKey,
255+
Data = any
256+
> = Args extends (readonly [...infer K])
257+
? ((...args: [...K]) => Result<Data>)
258+
: Args extends null
259+
? never
260+
: Args extends (infer T)
261+
? (...args: [T]) => Result<Data>
262+
: never
263+
264+
interface SWRInfiniteHook {
265+
<Data = any, Error = any, Args extends ValueKey = ValueKey>(
266+
getKey: KeyLoader<Args>
267+
): SWRInfiniteResponse<Data, Error>
268+
<Data = any, Error = any, Args extends ValueKey = ValueKey>(
269+
getKey: KeyLoader<Args>,
270+
fn: InfiniteFetcher<Args, Data> | null
271+
): SWRInfiniteResponse<Data, Error>
272+
<Data = any, Error = any, Args extends ValueKey = ValueKey>(
273+
getKey: KeyLoader<Args>,
274+
config: SWRInfiniteConfiguration<Data, Error, Args> | undefined
275+
): SWRInfiniteResponse<Data, Error>
276+
<Data = any, Error = any, Args extends ValueKey = ValueKey>(
277+
...args: [
278+
KeyLoader<Args>,
279+
InfiniteFetcher<Args, Data> | null,
280+
SWRInfiniteConfiguration<Data, Error, Args> | undefined
281+
]
282+
): SWRInfiniteResponse<Data, Error>
283+
}
263284

264285
export default withMiddleware(useSWR, infinite) as SWRInfiniteHook
265286
export { SWRInfiniteConfiguration, SWRInfiniteResponse }

infinite/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { SWRConfiguration, Fetcher, SWRResponse } from 'swr'
1+
import { SWRConfiguration, Fetcher, SWRResponse, ValueKey } from 'swr'
22

33
export type SWRInfiniteConfiguration<
44
Data = any,
5-
Error = any
6-
> = SWRConfiguration<Data[], Error, Fetcher<Data[]>> & {
5+
Error = any,
6+
Args extends ValueKey = ValueKey
7+
> = SWRConfiguration<Data[], Error, Args, Fetcher<Data[], Args>> & {
78
initialSize?: number
89
revalidateAll?: boolean
910
persistSize?: boolean

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,7 @@ export {
2020
SWRHook,
2121
Fetcher,
2222
MutatorCallback,
23-
Middleware
23+
Middleware,
24+
ValueKey,
25+
Result
2426
} from './types'

src/types.ts

Lines changed: 82 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,36 @@
11
import * as revalidateEvents from './constants/revalidate-events'
22

3-
export type Fetcher<Data> = (...args: any) => Data | Promise<Data>
3+
export type Result<T = unknown> = T | Promise<T>
4+
5+
export type Fetcher<Data = unknown, Args extends Key = Key> =
6+
/**
7+
* () => [{ foo: string }, { bar: number }] | null
8+
*
9+
* () => ( [{ foo: string }, { bar: number } ] as const | null )
10+
*/
11+
Args extends (() => readonly [...infer K] | null)
12+
? ((...args: [...K]) => Result<Data>)
13+
: /**
14+
* [{ foo: string }, { bar: number } ] | null
15+
*
16+
* [{ foo: string }, { bar: number } ] as const | null
17+
*/
18+
Args extends (readonly [...infer K])
19+
? ((...args: [...K]) => Result<Data>)
20+
: /**
21+
* () => string | null
22+
* () => Record<any, any> | null
23+
*/
24+
Args extends (() => infer T | null)
25+
? (...args: [T]) => Result<Data>
26+
: /**
27+
* string | null | Record<any,any>
28+
*/
29+
Args extends null
30+
? never
31+
: Args extends (infer T)
32+
? (...args: [T]) => Result<Data>
33+
: never
434

535
// Configuration types that are only used internally, not exposed to the user.
636
export interface InternalConfiguration {
@@ -11,7 +41,8 @@ export interface InternalConfiguration {
1141
export interface PublicConfiguration<
1242
Data = any,
1343
Error = any,
14-
Fn extends Fetcher<Data> = Fetcher<Data>
44+
Args extends Key = Key,
45+
Fn = Fetcher<Data, Args>
1546
> {
1647
errorRetryInterval: number
1748
errorRetryCount?: number
@@ -35,22 +66,22 @@ export interface PublicConfiguration<
3566
isPaused: () => boolean
3667
onLoadingSlow: (
3768
key: string,
38-
config: Readonly<PublicConfiguration<Data, Error>>
69+
config: Readonly<PublicConfiguration<Data, Error, Args, Fn>>
3970
) => void
4071
onSuccess: (
4172
data: Data,
4273
key: string,
43-
config: Readonly<PublicConfiguration<Data, Error>>
74+
config: Readonly<PublicConfiguration<Data, Error, Args, Fn>>
4475
) => void
4576
onError: (
4677
err: Error,
4778
key: string,
48-
config: Readonly<PublicConfiguration<Data, Error>>
79+
config: Readonly<PublicConfiguration<Data, Error, Args, Fn>>
4980
) => void
5081
onErrorRetry: (
5182
err: Error,
5283
key: string,
53-
config: Readonly<PublicConfiguration<Data, Error>>,
84+
config: Readonly<PublicConfiguration<Data, Error, Args, Fn>>,
5485
revalidate: Revalidator,
5586
revalidateOpts: Required<RevalidatorOptions>
5687
) => void
@@ -67,29 +98,51 @@ export type ConfigOptions = {
6798
initFocus: (callback: () => void) => (() => void) | void
6899
initReconnect: (callback: () => void) => (() => void) | void
69100
}
70-
71-
export type SWRHook = <Data = any, Error = any>(
72-
...args:
73-
| readonly [Key]
74-
| readonly [Key, Fetcher<Data> | null]
75-
| readonly [Key, SWRConfiguration<Data, Error> | undefined]
76-
| readonly [
77-
Key,
78-
Fetcher<Data> | null,
79-
SWRConfiguration<Data, Error> | undefined
80-
]
81-
) => SWRResponse<Data, Error>
101+
export interface SWRHook {
102+
<Data = any, Error = any, Args extends Key = Key>(args: Args): SWRResponse<
103+
Data,
104+
Error
105+
>
106+
<Data = any, Error = any, Args extends Key = Key>(
107+
args: Args,
108+
fn: Fetcher<Data, Args> | null
109+
): SWRResponse<Data, Error>
110+
<Data = any, Error = any, Args extends Key = Key>(
111+
args: Args,
112+
config: SWRConfiguration<Data, Error, Args, Fetcher<Data, Args>> | undefined
113+
): SWRResponse<Data, Error>
114+
<Data = any, Error = any, Args extends Key = Key>(
115+
args: Args,
116+
fn: Fetcher<Data, Args>,
117+
config: SWRConfiguration<Data, Error, Args, Fetcher<Data, Args>>
118+
): SWRResponse<Data, Error>
119+
<Data = any, Error = any, Args extends Key = Key>(
120+
...args:
121+
| [Args]
122+
| [Args, Fetcher<Data, Args> | null]
123+
| [
124+
Args,
125+
SWRConfiguration<Data, Error, Args, Fetcher<Data, Args>> | undefined
126+
]
127+
| [
128+
Args,
129+
Fetcher<Data, Key> | null,
130+
SWRConfiguration<Data, Error, Args, Fetcher<Data, Args>>
131+
]
132+
): SWRResponse<Data, Error>
133+
}
82134

83135
// Middlewares guarantee that a SWRHook receives a key, fetcher, and config as the argument
84-
type SWRHookWithMiddleware = <Data = any, Error = any>(
85-
key: Key,
86-
fetcher: Fetcher<Data> | null,
136+
type SWRHookWithMiddleware = <Data = any, Error = any, Args extends Key = Key>(
137+
key: Args,
138+
fetcher: Fetcher<Data, Args> | null,
87139
config: SWRConfiguration<Data, Error>
88140
) => SWRResponse<Data, Error>
89141

90142
export type Middleware = (useSWRNext: SWRHook) => SWRHookWithMiddleware
91-
92-
export type ValueKey = string | any[] | object | null
143+
export type TupleKey = [any, ...unknown[]] | readonly [any, ...unknown[]]
144+
export type ValueKey = string | null | TupleKey | Record<any, any>
145+
export type Key = ValueKey | (() => ValueKey)
93146

94147
export type MutatorCallback<Data = any> = (
95148
currentValue?: Data
@@ -142,10 +195,9 @@ export type KeyedMutator<Data> = (
142195
export type SWRConfiguration<
143196
Data = any,
144197
Error = any,
145-
Fn extends Fetcher<Data> = Fetcher<Data>
146-
> = Partial<PublicConfiguration<Data, Error, Fn>>
147-
148-
export type Key = ValueKey | (() => ValueKey)
198+
Args extends Key = Key,
199+
Fn = Fetcher<any, Args>
200+
> = Partial<PublicConfiguration<Data, Error, Args, Fn>>
149201

150202
export interface SWRResponse<Data, Error> {
151203
data?: Data
@@ -154,9 +206,10 @@ export interface SWRResponse<Data, Error> {
154206
isValidating: boolean
155207
}
156208

157-
export type KeyLoader<Data = any> =
158-
| ((index: number, previousPageData: Data | null) => ValueKey)
209+
export type KeyLoader<Args extends ValueKey = ValueKey> =
210+
| ((index: number, previousPageData: any | null) => Args)
159211
| null
212+
160213
export interface RevalidatorOptions {
161214
retryCount?: number
162215
dedupe?: boolean

src/use-swr.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export const useSWRHandler = <Data = any, Error = any>(
173173

174174
// Start the request and keep the timestamp.
175175
CONCURRENT_PROMISES_TS[key] = getTimestamp()
176-
CONCURRENT_PROMISES[key] = fn.apply(fn, fnArgs)
176+
CONCURRENT_PROMISES[key] = fn(...fnArgs)
177177
}
178178

179179
// Wait until the ongoing request is done. Deduplication is also

src/utils/normalize-args.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { Key, Fetcher, SWRConfiguration } from '../types'
44

55
export const normalize = <KeyType = Key, Data = any>(
66
args:
7-
| readonly [KeyType]
8-
| readonly [KeyType, Fetcher<Data> | null]
9-
| readonly [KeyType, SWRConfiguration | undefined]
10-
| readonly [KeyType, Fetcher<Data> | null, SWRConfiguration | undefined]
7+
| [KeyType]
8+
| [KeyType, Fetcher<Data> | null]
9+
| [KeyType, SWRConfiguration | undefined]
10+
| [KeyType, Fetcher<Data> | null, SWRConfiguration | undefined]
1111
): [KeyType, Fetcher<Data> | null, Partial<SWRConfiguration<Data>>] => {
1212
return isFunction(args[1])
1313
? [args[0], args[1], args[2] || {}]

src/utils/serialize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { isFunction } from './helper'
33

44
import { Key } from '../types'
55

6-
export const serialize = (key: Key): [string, any, string, string] => {
6+
export const serialize = (key: Key): [string, any[], string, string] => {
77
if (isFunction(key)) {
88
try {
99
key = key()

src/utils/with-middleware.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ export const withMiddleware = (
99
): SWRHook => {
1010
return <Data = any, Error = any>(
1111
...args:
12-
| readonly [Key]
13-
| readonly [Key, Fetcher<Data> | null]
14-
| readonly [Key, SWRConfiguration | undefined]
15-
| readonly [Key, Fetcher<Data> | null, SWRConfiguration | undefined]
12+
| [Key]
13+
| [Key, Fetcher<Data> | null]
14+
| [Key, SWRConfiguration | undefined]
15+
| [Key, Fetcher<Data> | null, SWRConfiguration | undefined]
1616
) => {
1717
const [key, fn, config] = normalize(args)
1818
config.use = (config.use || []).concat(middleware)

0 commit comments

Comments
 (0)