From 342b82c818454b05c05ae7294c531674926539db Mon Sep 17 00:00:00 2001 From: Yuta Osawa Date: Tue, 18 Apr 2023 06:42:23 +0900 Subject: [PATCH] fix: pass serialized args to preload fetcher (#2564) --- _internal/utils/preload.ts | 33 +++++++++++++++++++++---- core/use-swr.ts | 5 ++-- test/type/preload.ts | 46 +++++++++++++++++++++++++++++++++++ test/use-swr-preload.test.tsx | 12 +++++++++ 4 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 test/type/preload.ts diff --git a/_internal/utils/preload.ts b/_internal/utils/preload.ts index eae6e4f2b..cdcbf2751 100644 --- a/_internal/utils/preload.ts +++ b/_internal/utils/preload.ts @@ -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 = (key_: Key, fetcher: BareFetcher) => { - 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 + : SWRKey extends infer Arg + ? (arg: Arg) => FetcherResponse + : never + +export const preload = < + Data = any, + SWRKey extends Key = Key, + Fetcher extends BareFetcher = PreloadFetcher +>( + key_: SWRKey, + fetcher: Fetcher +): ReturnType => { + 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 PRELOAD[key] = req return req } @@ -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) { diff --git a/core/use-swr.ts b/core/use-swr.ts index 46a8f81c8..7359444d0 100644 --- a/core/use-swr.ts +++ b/core/use-swr.ts @@ -72,11 +72,10 @@ export const useSWRHandler = ( 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. diff --git a/test/type/preload.ts b/test/type/preload.ts new file mode 100644 index 000000000..6fe70f080 --- /dev/null +++ b/test/type/preload.ts @@ -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, typeof data1>>(true) + + const data2 = preload( + () => 'key', + () => 'value' as const + ) + expectType>(true) + + const data3 = preload<'value'>( + () => 'key', + () => 'value' as const + ) + // specifing a generic param breaks the rest type inference so get FetcherResponse<"value"> + expectType, typeof data3>>(true) + + preload('key', key => { + expectType>(true) + }) + + preload<'value'>( + 'key', + ( + // @ts-expect-error -- infered any implicitly + key + ) => { + return 'value' as const + } + ) + + preload(['key', 1], keys => { + expectType>(true) + }) + + preload( + () => 'key' as const, + key => { + expectType>(true) + } + ) +} diff --git a/test/use-swr-preload.test.tsx b/test/use-swr-preload.test.tsx index 2d78163b5..c69e11fb0 100644 --- a/test/use-swr-preload.test.tsx +++ b/test/use-swr-preload.test.tsx @@ -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) + }) })