Being explicit with your own React Hook + TypeScript return type

featured
February 10, 2019
💫 Originally posted here. Broken? Let me know ~

Photo by Rafaela Biazi on Unsplash

TheAifam5 graciously reported an issue for react-use-localstorage that even though it was written using TypeScript, it wasn’t distributing TypeScript type declaration file.

react-use-localstorage is a React Hook that returns an array of Local Storage item and a way to set it

Initially written in JavaScript but recently converted to TypeScript to learn TypeScript.

I followed TheAifam5’s instruction to generate a type file and distributed it.

But when I tried to use the new distrubution within a React + TypeScript project, I was getting the following error.

[ts] Cannot invoke an expression whose type lacks a call signature.
Type 'string | ((item: string) => void)' has no compatible call signatures. [2349]
view raw error hosted with ❤ by GitHub
Error Message
Error Message in VS Code

Below is the full source code for useLocalStorage.

import React from "react";
export default function useLocalStorage(key: string, initialValue: string = "") {
const [item, setValue] = React.useState(() => {
const value = localStorage.getItem(key) || initialValue;
localStorage.setItem(key, value);
return value;
});
const setItem = (item: string) => {
setValue(item);
window.localStorage.setItem(key, item);
};
return [item, setItem];
}
useLocalStorage – react-use-localstorage on NPM

As you can see, useLocalStorage returns an array of [item, setItem].

When auto-generating a type file using tsc, TypeScript generates following definition code.

export default function useLocalStorage(key: string, initialValue?: string): (string | ((item: string) => void))[];
//# sourceMappingURL=index.d.ts.map
view raw bad-index.d.ts hosted with ❤ by GitHub
Bad return type

TypeScript inferred the return type as (string | ((item: string) => void))[] which is not right.

So to fix it you need to explicitly declare the return type of useLocalStorage to generate a correct type definition.

export default function useLocalStorage(key: string, initialValue: string = "")
: [string, (item: string) => void] {
// rest omitted for brevity ...
}
view raw useLocalStorage.ts hosted with ❤ by GitHub
Return type specified explicitly

You can now see that TypeScript has generated the definition correctly.

export default function useLocalStorage(key: string, initialValue?: string): [string, (item: string) => void];
//# sourceMappingURL=index.d.ts.map
view raw good-index.d.ts hosted with ❤ by GitHub
Good return type

And TypeScript is happy (in VS Code).

TypeScript is now 🙂


🤔 Question to readers

Does anyone know why TypeScript wasn’t able to infer the return type correctly?

*** UPDATE: 2019-02-12 ***

pgrizzay & AngularBeginner have generously answered the question above in Reddit.

The gist is that, TypeScript does not infer tuple type variable because there isn’t enough information.


🏔 Resources