Afs35mm
Afs35mm

Reputation: 649

This expression is not callable when destructuring an array of React hooks in TypeScript

In my React TS component I have a bunch of fields, contrived example below, that check a specific condition, and if it is not met, set the specific field error to true, to be reflected and the component DOM (and thus not submit) However when I have the code below it throws an expression not callable on the setErr function.

const App = () => {
  const [name, setName] = React.useState("");
  const [email, setEmail] = React.useState("");
  const [nameError, setNameError] = React.useState(false);
  const [emailError, setEmailError] = React.useState(false);
  return (
    <div className="App">
      <input
        type="text"
        value={name}
        style={{
          border: `1 px solid ${nameError ? "red" : "black"}`
        }}
        onChange={e => {
          setName(e.target.value);
        }}
      />
      <input
        type="text"
        value={email}
        onChange={e => {
          setEmail(e.target.value);
        }}
        style={{
          border: `1 px solid ${emailError ? "red" : "black"}`
        }}
      />
      <button
        onClick={() => {
          const errors = [
            [setNameError, name.length],
            [setEmailError, email.length]
          ];

          let canSubmit = true;
          errors.forEach(validation => {
            const [setErr, condition] = validation;
            console.log(!condition);
            if (!condition) {
              canSubmit = false;
              setErr(true); // <---- ERROR HERE
            }
          });

          if (canSubmit) { /* submit the form */ } 
        }}
      >
        submit
      </button>
    </div>
  );
};

This only errors in TypeScript as it works fine in vanilla/jsx. And doesn't compile in some build systems.

Full error is:

This expression is not callable.
  Not all constituents of type 'string | number | boolean | Dispatch<SetStateAction<boolean>>' are callable.
    Type 'string' has no call signatures.

I am especially confused why it thinks setErr is of type string, when it should be equal to the setNameError function destructured from useState.

Upvotes: 4

Views: 6262

Answers (2)

Jonas Wilms
Jonas Wilms

Reputation: 138267

All you need is to add as const to the errors declaration:

  const errors = [
        [setNameError, name.length],
        [setEmailError, email.length]
   ] as const;

That way, the arrays won't be typed as arrays but as tuples.

Upvotes: 9

gqstav
gqstav

Reputation: 2072

The inferred type of errors is what's shooting you down here. By your error message we can derive that const errors: (string | number | boolean | Dispatch<SetStateAction<boolean>>)[][], hence typescript infers that the array elements can be a bunch of things, some of which are not callable. Instead you could objectify it, and the types inferred will be assigned to keys, allowing you do deconstruct and call properly, i.e.

<button
  onClick={() => {
    const errors = [
      {setError:setNameError, condition:name.length},
      {setError:setEmailError, condition:email.length}
    ];

    let canSubmit = true;
    errors.forEach(validation => {
      const {setError, condition} = validation;
      console.log(!condition);
      if (!condition) {
        canSubmit = false;
        setError(true); // <---- ERROR HERE
      }
    });

    if (canSubmit) { /* submit the form */ } 
  }}
>

Upvotes: 0

Related Questions