Stephen A. Lizcano
Stephen A. Lizcano

Reputation: 442

How to type custom React hooks that are created in JS and used in a TSX file?

I have a custom hook that is unfortunately written in pure JS that I am using in a TypeScript .tsx file.

I'm calling it like

const [toggleDictation, dictationResults] = useDictation()

  useEffect(() => {

    //start recognizing
    toggleDictation();

  }, []);

I get an error in the compiler that says:

This expression is not callable.
  Not all constituents of type 'string | boolean | never[] | (() => Promise<void>)' are callable.
    Type 'string' has no call signatures.ts(2349)

the toggleDictation function in the custom JS hook is written as:


  const toggleDictation = async () => {
    try {
      if (!speechHasStarted) {
        await _startRecognizing();

        return;
      } else {
        await _stopRecognizing();
        return;
      }
    } catch (e) {
      //eslint-disable-next-line
      console.error("Error in toggleDictation, ", e);
      return;
    }
  };

Is there a way to typecast or assert this to set the function type correctly to remove the error?

Upvotes: 1

Views: 3474

Answers (2)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250336

The real problem here is with the useDictation function. By default, even in typescript a function returning an array is an array not a tuple type.

const toggleDictation = async () => { }
const useDictation = () => { // returns Array<(() => Promise<void>) | never[]>
    return [toggleDictation, []]
}

const [tg, arr] = useDictation(); // tg is (() => Promise<void>) | never[] so uncallable

Playground Link

In ts you can add an as const or an explicit type annotation to fix this:

const toggleDictation = async () => { }
const useDictation = () => { // returns Array<(() => Promise<void>) | never[]>
    return [toggleDictation, []] as const
}

const [tg, arr] = useDictation(); // tg is now () => Promise<void>
tg() // ok

Playground Link

For JS we have some options, if you control the js, you can add jsdoc types and they will be picked up by TS:

const toggleDictation = async () => { }

/**
 * @returns {[()=> Promise<void>, string[])]}
 */
const useDictation = () => {
    return [toggleDictation, []]
}

let [tg, d] = useDictation(); // tg is ()=> Promise<void>
tg() // ok in ts as well 


Playground Link

Or you can add a declaration file, but that really depends on your setup. If you create a declaration for a module, you will not get any more inference from js. You could generate a d.ts from the js files (available since 3.7 PR) and correct any declarations (such as the return type of useDictation)

declare const useDictation : () => [()=> Promise<void>, string[]]

let [tg, d] = useDictation(); // tg is ()=> Promise<void>
tg() // ok 

Playground Link

Upvotes: 4

Magnus Jensen
Magnus Jensen

Reputation: 49

Typing this shouldn't have an impact on the compiled result. Of course, if this works in a plain JS file, what i'm saying is false, but the problem seems to lie in the implementation rather than the lack of typing.

Upvotes: 1

Related Questions