vercel / swr

React Hooks for Data Fetching
https://swr.vercel.app
MIT License
30.53k stars 1.22k forks source link

typescript is incorrect when using different fetchers but the same key #2925

Open tjallingt opened 7 months ago

tjallingt commented 7 months ago

Bug report

Description / Observed Behavior

import useSWR from "swr";

async function fetchOne(key: string): Promise<string> {
  return "1";
}

async function fetchTwo(key: string): Promise<number> {
  return 2;
}

export default function One() {
  let { data } = useSWR("test", fetchOne);

  if (data != null) {
    data.charCodeAt(0); // Error: data.charCodeAt is not a function
  }

  return (
    <>
      <div>one {data}</div>
      <Two />
    </>
  );
}

function Two() {
  let { data } = useSWR("test", fetchTwo);

  return <div>two {data}</div>;
}

results in

Error: data.charCodeAt is not a function

Despite having no TypeScript errors

Expected Behavior

one 1
two 2

Repro Steps / Code Example

https://codesandbox.io/p/sandbox/test-swr-mfvvvg

Additional Context

SWR version: 2.2.4 & 2.25

garyhuntddn commented 6 months ago

Ha ha - I can see what you’d like it to do, but that’ll never be possible given that typescript is a facade on top of JavaScript - you’re telling it what you expect, but in no way is the JavaScript contracted to do that.

the general use case of swr is to call fetch, and most assume that the json they then attempt to cast into a type will conform to that type - but that won’t be the case on many occasions. Therefore always treat swr as an api boundary and never trust the results to be what you hope them to be.

in this particular example you’ve simply used the same cache key (‘test’) for multiple things and any library would suffer the same problem.

tjallingt commented 6 months ago

But I would expect the fetcher to, implicitly, be part of the cache key. So that "test" + "fetchOne" is cached in a different slot than "test" + "fetchTwo". That would also fix the error with the types.

garyhuntddn commented 6 months ago

I've never seen it done - but there's no harm in you creating your own standard...

const { data } = useSWR( [ "test", fetchOne ], fetchOne);

and

const { data } = useSWR( [ "test", fetchTwo ], fetchTwo);

Would then include the fetcher in the key - but once again - remember that in real world use - what gets returned from a cache of any kind might not be what you expect - so you should code more defensively and expect the unexpected.