Sometimes, you want to make your function return different result depends on the parameter.
Take, for example the following React custom hooks:
ts
export function usePreloadImage(imageSrc: string, eager = true) {/* if it is eager,then set loadIt to true, else make loadIt false */const [loadIt, setLoadIt] = React.useState(eager);React.useEffect(() => {if (imageSrc && loadIt) {// if loadIt is true, load the imageconst image = new Image();image.src = imageSrc;}}, [loadIt, imageSrc]);if (eager) {/* if eager, this hooks should NOT returna function because the image already loaded */return;}/* if not eager, returns a functionthat user can call to start load the image */return () => setLoadIt(true);}
ts
export function usePreloadImage(imageSrc: string, eager = true) {/* if it is eager,then set loadIt to true, else make loadIt false */const [loadIt, setLoadIt] = React.useState(eager);React.useEffect(() => {if (imageSrc && loadIt) {// if loadIt is true, load the imageconst image = new Image();image.src = imageSrc;}}, [loadIt, imageSrc]);if (eager) {/* if eager, this hooks should NOT returna function because the image already loaded */return;}/* if not eager, returns a functionthat user can call to start load the image */return () => setLoadIt(true);}
To use it:
tsx
export const Page = () => {// this image will be loaded when on mountusePreloadImage('http://placecorgi.com/200/200');// this image only load when you hover over the buttonconst loadLargeImage = usePreloadImage('http://placecorgi.com/300/300', false);return (<div><p>Open DevTools (F12) Network Tabs to verify</p><buttononMouseEnter={() => {// typecheck as typescript infer this may be undefined// highlight-next-lineif (loadLargeImage) {loadLargeImage();}}}>Hover Me</button></div>);};
tsx
export const Page = () => {// this image will be loaded when on mountusePreloadImage('http://placecorgi.com/200/200');// this image only load when you hover over the buttonconst loadLargeImage = usePreloadImage('http://placecorgi.com/300/300', false);return (<div><p>Open DevTools (F12) Network Tabs to verify</p><buttononMouseEnter={() => {// typecheck as typescript infer this may be undefined// highlight-next-lineif (loadLargeImage) {loadLargeImage();}}}>Hover Me</button></div>);};
See it in action:
jsx
function usePreloadImage(imageSrc, eager = true) {const [loadIt, setLoadIt] = React.useState(eager);React.useEffect(() => {if (imageSrc && loadIt) {const image = new Image();image.src = imageSrc;}}, [loadIt, imageSrc]);if (eager) {return;}return () => setLoadIt(true);}const Page = () => {// this image will be loaded when on mountusePreloadImage('http://placecorgi.com/200/200');// this image only load when you hover over the buttonconst loadLargeImage = usePreloadImage('http://placecorgi.com/300/300', false);return (<div><p>Open DevTools (F12) Network Tabs to verify</p><buttononMouseEnter={() => {loadLargeImage();}}>Hover Me</button></div>);};const Container = () => {const [key, setKey] = React.useState(0);return (<div><button onClick={() => setKey(key + 1)} className="btn btn-raised btn-primary">Reload App</button><Page key={key} /></div>);};render(<Container />);
jsx
function usePreloadImage(imageSrc, eager = true) {const [loadIt, setLoadIt] = React.useState(eager);React.useEffect(() => {if (imageSrc && loadIt) {const image = new Image();image.src = imageSrc;}}, [loadIt, imageSrc]);if (eager) {return;}return () => setLoadIt(true);}const Page = () => {// this image will be loaded when on mountusePreloadImage('http://placecorgi.com/200/200');// this image only load when you hover over the buttonconst loadLargeImage = usePreloadImage('http://placecorgi.com/300/300', false);return (<div><p>Open DevTools (F12) Network Tabs to verify</p><buttononMouseEnter={() => {loadLargeImage();}}>Hover Me</button></div>);};const Container = () => {const [key, setKey] = React.useState(0);return (<div><button onClick={() => setKey(key + 1)} className="btn btn-raised btn-primary">Reload App</button><Page key={key} /></div>);};render(<Container />);
This works, but I dislike that as a user of the custom hook, I need to do a check on the type of the returned callback of the custom hook. We know that when we pass false
as second parameter to the usePreloadImage
hook, it will definitely return a function. Now we do that type checking not because it is necessary, but just to shut TypeScript up.
This is because TypeScript is not that smart to infer that logic; it only sees that there are two possible outcomes of usePreloadImage
function, which is either undefined
or a function.
We need to teach TypeScript to recognize that patterns by overload the function:
ts
// highlight-startexport function usePreloadImage(imageSrc: string, eager?: true): void;export function usePreloadImage(imageSrc: string, eager: false): () => void;// highlight-endexport function usePreloadImage(imageSrc: string, eager = true) {const [loadIt, setLoadIt] = React.useState(eager);React.useEffect(() => {if (imageSrc && loadIt) {const image = new Image();image.src = imageSrc;}}, [loadIt, imageSrc]);if (eager) {return;}return () => setLoadIt(true);}
ts
// highlight-startexport function usePreloadImage(imageSrc: string, eager?: true): void;export function usePreloadImage(imageSrc: string, eager: false): () => void;// highlight-endexport function usePreloadImage(imageSrc: string, eager = true) {const [loadIt, setLoadIt] = React.useState(eager);React.useEffect(() => {if (imageSrc && loadIt) {const image = new Image();image.src = imageSrc;}}, [loadIt, imageSrc]);if (eager) {return;}return () => setLoadIt(true);}
Now TypeScript can infer the returned type correctly.