Skip to content

Type useSWR fetcher #1477

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 5 commits into from
Sep 26, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions immutable/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import useSWR, { Middleware, SWRHook } from 'swr'
import useSWR, { Middleware } from 'swr'
import { withMiddleware } from '../src/utils/with-middleware'

export const immutable: Middleware = useSWRNext => (key, fetcher, config) => {
Expand All @@ -9,4 +9,4 @@ export const immutable: Middleware = useSWRNext => (key, fetcher, config) => {
return useSWRNext(key, fetcher, config)
}

export default withMiddleware(useSWR as SWRHook, immutable)
export default withMiddleware(useSWR, immutable)
63 changes: 43 additions & 20 deletions infinite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import useSWR, {
Fetcher,
SWRHook,
MutatorCallback,
Middleware
Middleware,
ValueKey,
Result
} from 'swr'
import { useIsomorphicLayoutEffect } from '../src/utils/env'
import { serialize } from '../src/utils/serialize'
Expand All @@ -18,18 +20,20 @@ import { SWRInfiniteConfiguration, SWRInfiniteResponse } from './types'

const INFINITE_PREFIX = '$inf$'

const getFirstPageKey = (getKey: KeyLoader<any>) => {
const getFirstPageKey = (getKey: KeyLoader) => {
return serialize(getKey ? getKey(0, null) : null)[0]
}

export const unstable_serialize = (getKey: KeyLoader<any>) => {
export const unstable_serialize = (getKey: KeyLoader) => {
return INFINITE_PREFIX + getFirstPageKey(getKey)
}

export const infinite = ((<Data, Error>(useSWRNext: SWRHook) => (
getKey: KeyLoader<Data>,
export const infinite = ((<Data, Error, Args extends ValueKey>(
useSWRNext: SWRHook
) => (
getKey: KeyLoader<Args>,
fn: Fetcher<Data> | null,
config: typeof SWRConfig.default & SWRInfiniteConfiguration<Data, Error>
config: typeof SWRConfig.default & SWRInfiniteConfiguration<Data, Error, Args>
): SWRInfiniteResponse<Data, Error> => {
const rerender = useState({})[1]
const didMountRef = useRef<boolean>(false)
Expand Down Expand Up @@ -246,20 +250,39 @@ export const infinite = ((<Data, Error>(useSWRNext: SWRHook) => (
} as SWRInfiniteResponse<Data, Error>
}) as unknown) as Middleware

type SWRInfiniteHook = <Data = any, Error = any>(
...args:
| readonly [KeyLoader<Data>]
| readonly [KeyLoader<Data>, Fetcher<Data> | null]
| readonly [
KeyLoader<Data>,
SWRInfiniteConfiguration<Data, Error> | undefined
]
| readonly [
KeyLoader<Data>,
Fetcher<Data> | null,
SWRInfiniteConfiguration<Data, Error> | undefined
]
) => SWRInfiniteResponse<Data, Error>
export type InfiniteFetcher<
Args extends ValueKey = ValueKey,
Data = unknown
> = Args extends (readonly [...infer K])
? ((...args: [...K]) => Result<Data>)
: Args extends (string | null)
? Args extends (Record<any, any> | null)
? never
: (...args: [string]) => Result<Data>
: Args extends (infer T)
? (...args: [T]) => Result<Data>
: never

interface SWRInfiniteHook {
<Data = any, Error = any, Args extends ValueKey = ValueKey>(
getKey: KeyLoader<Args>
): SWRInfiniteResponse<Data, Error>
<Data = any, Error = any, Args extends ValueKey = ValueKey>(
getKey: KeyLoader<Args>,
fn: InfiniteFetcher<Args, Data> | null
): SWRInfiniteResponse<Data, Error>
<Data = any, Error = any, Args extends ValueKey = ValueKey>(
getKey: KeyLoader<Args>,
config: SWRInfiniteConfiguration<Data, Error, Args> | undefined
): SWRInfiniteResponse<Data, Error>
<Data = any, Error = any, Args extends ValueKey = ValueKey>(
...args: [
KeyLoader<Args>,
InfiniteFetcher<Args, Data> | null,
SWRInfiniteConfiguration<Data, Error, Args> | undefined
]
): SWRInfiniteResponse<Data, Error>
}

export default withMiddleware(useSWR, infinite) as SWRInfiniteHook
export { SWRInfiniteConfiguration, SWRInfiniteResponse }
7 changes: 4 additions & 3 deletions infinite/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { SWRConfiguration, Fetcher, SWRResponse } from 'swr'
import { SWRConfiguration, Fetcher, SWRResponse, ValueKey } from 'swr'

export type SWRInfiniteConfiguration<
Data = any,
Error = any
> = SWRConfiguration<Data[], Error, Fetcher<Data[]>> & {
Error = any,
Args extends ValueKey = ValueKey
> = SWRConfiguration<Data[], Error, Args, Fetcher<Data[], Args>> & {
initialSize?: number
revalidateAll?: boolean
persistSize?: boolean
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ export {
SWRHook,
Fetcher,
MutatorCallback,
Middleware
Middleware,
ValueKey,
Result
} from './types'
121 changes: 92 additions & 29 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,46 @@
import * as revalidateEvents from './constants/revalidate-events'

export type Fetcher<Data> = (...args: any) => Data | Promise<Data>
export type Result<T = unknown> = T | Promise<T>

export type Fetcher<Data = unknown, Args extends Key = Key> =
/**
* () => [{ foo: string }, { bar: number }] | null
*
* () => ( [{ foo: string }, { bar: number } ] as const | null )
*/
Args extends (() => readonly [...infer K] | null)
? ((...args: [...K]) => Result<Data>)
/**
* [{ foo: string }, { bar: number } ] | null
*
* [{ foo: string }, { bar: number } ] as const | null
*/
: Args extends (readonly [...infer K])
? ((...args: [...K]) => Result<Data>)
/**
* () => string | null
* () => Record<any, any> | null
*/
: Args extends (() => infer T | null)
? (...args: [T]) => Result<Data>
/**
* string | null
*/
: Args extends (string | null)
/**
* when key is Record<any, any> | null
* use { foo: string, bar: number } | null as example
*
* the fetcher would be
* (arg0: string) => any | (arg0: { foo: string, bar: number }) => any
* so we add this condition to make (arg0: string) => any to be never
*/
? Args extends (Record<any, any> | null)
? never
: (...args: [string]) => Result<Data>
: Args extends (infer T)
? (...args: [T]) => Result<Data>
: never

// Configuration types that are only used internally, not exposed to the user.
export interface InternalConfiguration {
Expand All @@ -11,7 +51,8 @@ export interface InternalConfiguration {
export interface PublicConfiguration<
Data = any,
Error = any,
Fn extends Fetcher<Data> = Fetcher<Data>
Args extends Key = Key,
Fn = Fetcher<Data, Args>
> {
errorRetryInterval: number
errorRetryCount?: number
Expand All @@ -35,22 +76,22 @@ export interface PublicConfiguration<
isPaused: () => boolean
onLoadingSlow: (
key: string,
config: Readonly<PublicConfiguration<Data, Error>>
config: Readonly<PublicConfiguration<Data, Error, Args, Fn>>
) => void
onSuccess: (
data: Data,
key: string,
config: Readonly<PublicConfiguration<Data, Error>>
config: Readonly<PublicConfiguration<Data, Error, Args, Fn>>
) => void
onError: (
err: Error,
key: string,
config: Readonly<PublicConfiguration<Data, Error>>
config: Readonly<PublicConfiguration<Data, Error, Args, Fn>>
) => void
onErrorRetry: (
err: Error,
key: string,
config: Readonly<PublicConfiguration<Data, Error>>,
config: Readonly<PublicConfiguration<Data, Error, Args, Fn>>,
revalidate: Revalidator,
revalidateOpts: Required<RevalidatorOptions>
) => void
Expand All @@ -67,29 +108,51 @@ export type ConfigOptions = {
initFocus: (callback: () => void) => (() => void) | void
initReconnect: (callback: () => void) => (() => void) | void
}

export type SWRHook = <Data = any, Error = any>(
...args:
| readonly [Key]
| readonly [Key, Fetcher<Data> | null]
| readonly [Key, SWRConfiguration<Data, Error> | undefined]
| readonly [
Key,
Fetcher<Data> | null,
SWRConfiguration<Data, Error> | undefined
]
) => SWRResponse<Data, Error>
export interface SWRHook {
<Data = any, Error = any, Args extends Key = Key>(args: Args): SWRResponse<
Data,
Error
>
<Data = any, Error = any, Args extends Key = Key>(
args: Args,
fn: Fetcher<Data, Args> | null
): SWRResponse<Data, Error>
<Data = any, Error = any, Args extends Key = Key>(
args: Args,
config: SWRConfiguration<Data, Error, Args, Fetcher<Data, Args>> | undefined
): SWRResponse<Data, Error>
<Data = any, Error = any, Args extends Key = Key>(
args: Args,
fn: Fetcher<Data, Args>,
config: SWRConfiguration<Data, Error, Args, Fetcher<Data, Args>>
): SWRResponse<Data, Error>
<Data = any, Error = any, Args extends Key = Key>(
...args:
| [Args]
| [Args, Fetcher<Data, Args> | null]
| [
Args,
SWRConfiguration<Data, Error, Args, Fetcher<Data, Args>> | undefined
]
| [
Args,
Fetcher<Data, Key> | null,
SWRConfiguration<Data, Error, Args, Fetcher<Data, Args>>
]
): SWRResponse<Data, Error>
}

// Middlewares guarantee that a SWRHook receives a key, fetcher, and config as the argument
type SWRHookWithMiddleware = <Data = any, Error = any>(
key: Key,
fetcher: Fetcher<Data> | null,
type SWRHookWithMiddleware = <Data = any, Error = any, Args extends Key = Key>(
key: Args,
fetcher: Fetcher<Data, Args> | null,
config: SWRConfiguration<Data, Error>
) => SWRResponse<Data, Error>

export type Middleware = (useSWRNext: SWRHook) => SWRHookWithMiddleware

export type ValueKey = string | any[] | object | null
export type TupleKey = [any, ...unknown[]] | readonly [any, ...unknown[]]
export type ValueKey = string | null | TupleKey | Record<any, any>
export type Key = ValueKey | (() => ValueKey)

export type MutatorCallback<Data = any> = (
currentValue?: Data
Expand Down Expand Up @@ -142,10 +205,9 @@ export type KeyedMutator<Data> = (
export type SWRConfiguration<
Data = any,
Error = any,
Fn extends Fetcher<Data> = Fetcher<Data>
> = Partial<PublicConfiguration<Data, Error, Fn>>

export type Key = ValueKey | (() => ValueKey)
Args extends Key = Key,
Fn = Fetcher<any, Args>
> = Partial<PublicConfiguration<Data, Error, Args, Fn>>

export interface SWRResponse<Data, Error> {
data?: Data
Expand All @@ -154,9 +216,10 @@ export interface SWRResponse<Data, Error> {
isValidating: boolean
}

export type KeyLoader<Data = any> =
| ((index: number, previousPageData: Data | null) => ValueKey)
export type KeyLoader<Args extends ValueKey = ValueKey> =
| ((index: number, previousPageData: any | null) => Args)
| null

export interface RevalidatorOptions {
retryCount?: number
dedupe?: boolean
Expand Down
2 changes: 1 addition & 1 deletion src/use-swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export const useSWRHandler = <Data = any, Error = any>(

// Start the request and keep the timestamp.
CONCURRENT_PROMISES_TS[key] = getTimestamp()
CONCURRENT_PROMISES[key] = fn.apply(fn, fnArgs)
CONCURRENT_PROMISES[key] = fn(...fnArgs)
}

// Wait until the ongoing request is done. Deduplication is also
Expand Down
8 changes: 4 additions & 4 deletions src/utils/normalize-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { Key, Fetcher, SWRConfiguration } from '../types'

export const normalize = <KeyType = Key, Data = any>(
args:
| readonly [KeyType]
| readonly [KeyType, Fetcher<Data> | null]
| readonly [KeyType, SWRConfiguration | undefined]
| readonly [KeyType, Fetcher<Data> | null, SWRConfiguration | undefined]
| [KeyType]
| [KeyType, Fetcher<Data> | null]
| [KeyType, SWRConfiguration | undefined]
| [KeyType, Fetcher<Data> | null, SWRConfiguration | undefined]
): [KeyType, Fetcher<Data> | null, Partial<SWRConfiguration<Data>>] => {
return isFunction(args[1])
? [args[0], args[1], args[2] || {}]
Expand Down
2 changes: 1 addition & 1 deletion src/utils/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { isFunction } from './helper'

import { Key } from '../types'

export const serialize = (key: Key): [string, any, string, string] => {
export const serialize = (key: Key): [string, any[], string, string] => {
if (isFunction(key)) {
try {
key = key()
Expand Down
8 changes: 4 additions & 4 deletions src/utils/with-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export const withMiddleware = (
): SWRHook => {
return <Data = any, Error = any>(
...args:
| readonly [Key]
| readonly [Key, Fetcher<Data> | null]
| readonly [Key, SWRConfiguration | undefined]
| readonly [Key, Fetcher<Data> | null, SWRConfiguration | undefined]
| [Key]
| [Key, Fetcher<Data> | null]
| [Key, SWRConfiguration | undefined]
| [Key, Fetcher<Data> | null, SWRConfiguration | undefined]
) => {
const [key, fn, config] = normalize(args)
config.use = (config.use || []).concat(middleware)
Expand Down
Loading