Sunday, 15 October 2023

ForwardRefExoticComponent is not assignable to type ComponentType when using forwardRef with useImperativeHandle

We are working on an internal react library using typescript that has a component, here being called Parent, which we would like to be able to receive a custom component as a prop to be rendered, but also call the child component functions from Parent. To do this, we are using forwardRef with useImperativeHandle. To ensure all the components that are passed in as props have the correct functions, we have an interface called CustomFormProps, as shown here:

export interface CustomFormProps<T> {
  getValue: () => T | null;
  reset: () => void;
}

The Parent component has a prop type of ParentProps.

export interface ParentProps<T> {
  CustomFilterFormComponent?: React.ComponentType<
    CustomFormProps<T> & React.RefAttributes<T>
  >;
}

Parent is relatively straight forward, rendering a div, the custom component, and a button that triggers the function in the custom component.

const Parent = <T extends object>({
  CustomFilterFormComponent
}: ParentProps<T>) => {
  const customFormRef = useRef<any>(null);

  const customFormProps: any = {
    ref: customFormRef
  };

  const handleReset = () => {
    if (customFormRef.current) {
      customFormRef.current.reset();
    }
  };

  return (
    <div>
      <div>Hello</div>
      {CustomFilterFormComponent && (
        <CustomFilterFormComponent {...customFormProps} />
      )}
      <button onClick={handleReset}>Reset</button>
    </div>
  );
};

I have an example component that I pass into Parent. The code works and functions as it should, however on the line where we pass CustomFilterFormComponent to Parent we get this error:

Type 'ForwardRefExoticComponent<CustomFilterFormFilters & RefAttributes<CustomFormProps>>' is not assignable to type 'ComponentType<CustomFormProps<CustomFormProps> & RefAttributes<CustomFormProps>> | undefined'. Type 'ForwardRefExoticComponent<CustomFilterFormFilters & RefAttributes<CustomFormProps>>' is not assignable to type 'FunctionComponent<CustomFormProps<CustomFormProps> & RefAttributes<CustomFormProps>>'. Types of parameters 'props' and 'props' are incompatible. Type 'CustomFormProps<CustomFormProps> & RefAttributes<CustomFormProps>' is not assignable to type 'CustomFilterFormFilters & RefAttributes<CustomFormProps>'. Property 'upcs' is missing in type 'CustomFormProps<CustomFormProps> & RefAttributes<CustomFormProps>' but required in type 'CustomFilterFormFilters'.

Here is a link to a codesandbox where I was able to reproduce the error: https://codesandbox.io/s/loving-kapitsa-ysn9g7?file=/src/App.tsx

And here is the full source code:

import { useImperativeHandle, useRef, useState } from "react";
import "./styles.css";
import React from "react";

export interface CustomFormProps<T> {
  getValue: () => T | null;
  reset: () => void;
}

export interface ParentProps<T> {
  CustomFilterFormComponent?: React.ComponentType<
    CustomFormProps<T> & React.RefAttributes<T>
  >;
}

interface CustomFilterFormFilters {
  upcs: number[];
}

const YourCustomFilterFormComponent = React.forwardRef<
  CustomFormProps<CustomFilterFormFilters>,
  CustomFilterFormFilters
>(
  (
    props: CustomFilterFormFilters,
    ref: React.Ref<CustomFormProps<CustomFilterFormFilters>>
  ) => {
    const [upcs, setUpcs] = useState<any[]>(props.upcs ?? []);

    const upcOptions = [12345, 56789, 98635];

    const getValue = (): CustomFilterFormFilters | null => {
      if (upcs.length > 0) {
        return { upcs } as CustomFilterFormFilters;
      }

      return null;
    };

    useImperativeHandle(ref, () => ({
      getValue,
      reset: () => {
        setUpcs([]);
      }
    }));

    const handleSelection = (upc: number, selected: boolean) => {
      if (selected) {
        if (upcs.indexOf(upc) === -1) setUpcs([...upcs, upc]);
      } else {
        setUpcs(upcs.filter((q) => q != upc));
      }
    };

    return (
      <div>
        {upcOptions.map((upc, idx) => (
          <div key={idx}>
            <input
              type="checkbox"
              id={`chkCustomFilterForm-${idx}`}
              checked={upcs.indexOf(upc) > -1}
              onChange={(evt) => {
                handleSelection(upc, evt.target.checked);
              }}
            />
            <label htmlFor={`chkCustomFilterForm-${idx}`}>{upc}</label>
          </div>
        ))}
      </div>
    );
  }
);

const Parent = <T extends object>({
  CustomFilterFormComponent
}: ParentProps<T>) => {
  const customFormRef = useRef<any>(null);

  const customFormProps: any = {
    ref: customFormRef
  };

  const handleReset = () => {
    if (customFormRef.current) {
      customFormRef.current.reset();
    }
  };

  return (
    <div>
      <div>Hello</div>
      {CustomFilterFormComponent && (
        <CustomFilterFormComponent {...customFormProps} />
      )}
      <button onClick={handleReset}>Reset</button>
    </div>
  );
};

export default function App() {
  return (
    <div className="App">
      <Parent CustomFilterFormComponent={YourCustomFilterFormComponent} />
    </div>
  );
}

Any help is greatly appreciated. Thank you!



from ForwardRefExoticComponent is not assignable to type ComponentType when using forwardRef with useImperativeHandle

No comments:

Post a Comment