Skip to content

Commit

Permalink
fix: pass serialized args to preload fetcher (#2564)
Browse files Browse the repository at this point in the history
  • Loading branch information
oosawy committed Apr 17, 2023
1 parent a269839 commit 342b82c
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 8 deletions.
33 changes: 28 additions & 5 deletions _internal/utils/preload.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
import type { Middleware, Key, BareFetcher, GlobalState } from '../types'
import type {
Middleware,
Key,
BareFetcher,
GlobalState,
FetcherResponse
} from '../types'
import { serialize } from './serialize'
import { cache } from './config'
import { SWRGlobalState } from './global-state'

export const preload = <Data = any>(key_: Key, fetcher: BareFetcher<Data>) => {
const key = serialize(key_)[0]
// Basically same as Fetcher but without Conditional Fetching
type PreloadFetcher<
Data = unknown,
SWRKey extends Key = Key
> = SWRKey extends () => infer Arg
? (arg: Arg) => FetcherResponse<Data>
: SWRKey extends infer Arg
? (arg: Arg) => FetcherResponse<Data>
: never

export const preload = <
Data = any,
SWRKey extends Key = Key,
Fetcher extends BareFetcher = PreloadFetcher<Data, SWRKey>
>(
key_: SWRKey,
fetcher: Fetcher
): ReturnType<Fetcher> => {
const [key, fnArg] = serialize(key_)
const [, , , PRELOAD] = SWRGlobalState.get(cache) as GlobalState

// Prevent preload to be called multiple times before used.
if (PRELOAD[key]) return PRELOAD[key]

const req = fetcher(key_)
const req = fetcher(fnArg) as ReturnType<Fetcher>
PRELOAD[key] = req
return req
}
Expand All @@ -21,7 +44,7 @@ export const middleware: Middleware =
const fetcher =
fetcher_ &&
((...args: any[]) => {
const key = serialize(key_)[0]
const [key] = serialize(key_)
const [, , , PRELOAD] = SWRGlobalState.get(cache) as GlobalState
const req = PRELOAD[key]
if (req) {
Expand Down
5 changes: 2 additions & 3 deletions core/use-swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,10 @@ export const useSWRHandler = <Data = any, Error = any>(
cache
) as GlobalState

// `key` is the identifier of the SWR `data` state, `keyInfo` holds extra
// states such as `error` and `isValidating` inside,
// all of them are derived from `_key`.
// `key` is the identifier of the SWR internal state,
// `fnArg` is the argument/arguments parsed from the key, which will be passed
// to the fetcher.
// All of them are derived from `_key`.
const [key, fnArg] = serialize(_key)

// If it's the initial render of this hook.
Expand Down
46 changes: 46 additions & 0 deletions test/type/preload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { preload } from 'swr'
import { expectType } from './utils'
import type { Equal } from '@type-challenges/utils'

export function testPreload() {
const data1 = preload('key', () => Promise.resolve('value' as const))
expectType<Equal<Promise<'value'>, typeof data1>>(true)

const data2 = preload(
() => 'key',
() => 'value' as const
)
expectType<Equal<'value', typeof data2>>(true)

const data3 = preload<'value'>(
() => 'key',
() => 'value' as const
)
// specifing a generic param breaks the rest type inference so get FetcherResponse<"value">
expectType<Equal<'value' | Promise<'value'>, typeof data3>>(true)

preload('key', key => {
expectType<Equal<'key', typeof key>>(true)
})

preload<'value'>(
'key',
(
// @ts-expect-error -- infered any implicitly
key
) => {
return 'value' as const
}
)

preload(['key', 1], keys => {
expectType<Equal<[string, number], typeof keys>>(true)
})

preload(
() => 'key' as const,
key => {
expectType<Equal<'key', typeof key>>(true)
}
)
}
12 changes: 12 additions & 0 deletions test/use-swr-preload.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,16 @@ describe('useSWR - preload', () => {
expect(fetcherCount).toBe(1)
expect(renderCount).toBe(3)
})

it('should pass serialize key to fetcher', async () => {
const key = createKey()
let calledWith: string

const fetcher = (args: string) => {
calledWith = args
}

preload(() => key, fetcher)
expect(calledWith).toBe(key)
})
})

0 comments on commit 342b82c

Please sign in to comment.