Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explanation for this complicated 'is not assignable to type' error in typescript

I am trying to move my React project to Typescript, but without Typescript experience. I have a Component which is defined as follows (trimmed down):

interface InputProps {
  handleKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
  placeholder: string;
  style?: React.CSSProperties;
}
const Input: React.ForwardRefRenderFunction<HTMLInputElement, InputProps> = (
      ^^^^^
  { handleKeyDown, placeholder, style }: InputProps,
  ref: React.MutableRefObject<HTMLInputElement | null>
): JSX.Element => {
  return (
    <input
      ref={ref}
      onKeyDown={handleKeyDown}
      type="text"
      placeholder={placeholder}
      style={style}
    />
  );
};
export const ForwardedInput = React.forwardRef(Input);

Now I am getting the following TypeScript error on const Input, and it's a bit too complicated for me to untangle:

TS2322: Type '({ handleKeyDown, placeholder, style }: InputProps, ref: React.MutableRefObject<HTMLInputElement | null>) => JSX.Element' 
is not assignable to type 'ForwardRefRenderFunction<HTMLInputElement, InputProps>'.
  Types of parameters 'ref' and 'ref' are incompatible.
    Type 'MutableRefObject<HTMLInputElement | null> | ((instance: HTMLInputElement | null) => void) | null' is not assignable to type 'MutableRefObject<HTMLInputElement | null>'.
      Type 'null' is not assignable to type 'MutableRefObject<HTMLInputElement | null>'.

I'm guessing I need to fix this by changing ref: React.MutableRefObject<HTMLInputElement | null> into something else, but I don't know how, because I don't know what the error specifically means.

EDIT: The first answer suggested using generic parameters, so I adapted the function as follows:

const Input = ({ handleKeyDown, placeholder, style }: InputProps, ref: any) => {

I had to type props and ref to prevent typescript warnings (I use strict: "true"), and indeed this removes the warnings in the above code example.

But... using any resulted in:

ESLint: Unexpected any. Specify a different type.(@typescript-eslint/no-explicit-any)

According to this the use of any should be avoided and unknown should be used, but although this removes the warning in the function header, it caused a big error in the ref={ref} on the input component, stating the same incompatibility with ref as my first error. TypeScript is harder than I thought.

like image 529
raarts Avatar asked Apr 25 '20 22:04

raarts


1 Answers

You can use generic parameters on React.forwardRef

export const ForwardedInput = React.forwardRef<HTMLInputElement, InputProps>(
  (props, ref) => {
    const { handleKeyDown, placeholder, style } = props;
    return (
      <input
        ref={ref}
        onKeyDown={handleKeyDown}
        type="text"
        placeholder={placeholder}
        style={style}
      />
    );
  }
);

Playground link

If you need to separate React.forwardRef call and its function you can use React.ForwardRefRenderFunction generic type.

const ForwardInputRefFunction: React.ForwardRefRenderFunction<HTMLInputElement, InputProps> = (
  props,
  ref
) => {
  const { handleKeyDown, placeholder, style } = props;

  return (
    <input
      ref={ref}
      onKeyDown={handleKeyDown}
      type="text"
      placeholder={placeholder}
      style={style}
    />
  );
};

const ForwardedInput = React.forwardRef(ForwardInputRefFunction);

Playground link

You can explore new types by reading definition files in node_modules. To do it in Code sandbox use Cmd + Left-click hotkey.

like image 88
gzaripov Avatar answered Oct 07 '22 20:10

gzaripov