Tricking TypeScript Into Typing Untyped JavaScript Modules

You're trying to import a JS module from a package in a TypeScript file like this:

// file-of-sadness.ts

import { Query } from 'react-query/lib/core/query';

This is an internal JS module with no .d.ts files that declares types. TypeScript has no idea what to do. It's freaking out. 🥴

Could not find a declaration file for module 'react-query/lib/core/query'. '/project/node_modules/react-query/lib/core/query.js' implicitly has an 'any' type.

Try `npm i --save-dev @types/react-query` if it exists or add a new declaration (.d.ts) file containing `declare module 'react-query/lib/core/query';`ts(701

You've swerved off the Happy Path and headed straight for the muddy mountainside cliffs and you're about to have a Bad Day.

We can steer away from fiery doom by trying what TypeScript suggests and create a new declaration file next to our existing file:

// react-query-extended.d.ts

declare module 'react-query/lib/core/query' {
  
}

Awesome, no errors so far. But what do we put in it?!

If there is a type already available from the declarations that ship with a package what you can do is re-export them for the untyped module which will effectively let TypeScript know what typings it should expect to see for the untyped JS module.

Otherwise, you can manually provide the right typings for whatever it is you're trying to import.

In this case, React Query does export a Query type but the actual implementation is not exported. 😱 So that's why the cliff is coming up fast and our lives are flashing in front of us.

Tricking TS is dangerous territory only because the package is no longer the source of truth, you are and you could be wrong. The right way to do this is to have the package export what you need, if possible. But hey, sometimes we need to live on the edge a little! This is React Query v2 and that's Legacy now so there's no recourse for me!

declare module 'react-query/lib/core/query' {
  import { Query } from 'react-query';
  
  export { Query };
}

By using import and export within the declaration, we are implicitly using ES modules and now TypeScript understands what we're trying to import from the JS module.

We live to see another day and manage to get back onto the Happy Path (for now), phew. 😅