6

I'm trying to use defaultProps in forwardRef. When I use MessageBox without forwardRef it works well with defaultProps. But, when I use forwardRef with it an error occurs. how to solve it?

typescript: 3.4.5 "react": "^16.8.6",

import React, { forwardRef } from "react";

export interface MessageBoxProps {
  test: string;
  children: any;
}

const defaultProps: Partial<MessageBoxProps> = {
  test: "test"
};

const MessageBox = forwardRef<HTMLDivElement, MessageBoxProps>((props, ref) => {
  return <p>{props.test}</p>;
});

MessageBox.defaultProps = defaultProps;

export default MessageBox;

export default function App() {
  const ref = React.useRef<HTMLDivElement>(null);
  return (
    <div className="App">
      <Test ref={ref} />
    </div>
  );
}
Type '{ ref: RefObject<HTMLDivElement>; }' is missing the following properties from type 'MessageBoxProps': test, children

https://codesandbox.io/s/forwardref-with-typescript-knhu3?file=/src/App.tsx:83-244

2
  • your codesandbox working fine, not seen any error and the default prop also displaying Commented Jun 23, 2020 at 4:37
  • @RajKumar yes it works find in Codesnadbox, but when you go to App.tsx and see line no. 9, you can see an type error occurs and alos it dosn't works in the project which generated by CRA, but Codesandbox works. Commented Jun 26, 2020 at 4:12

2 Answers 2

2

The short answer seems to be : you can't use defaultProps with forward ref. You should use ES2015 default initializers as explained here

Here are my findings in @types/react source, maybe other people can help understand this further.

  1. You can't define default props on a component before feeding it to React.forwardRef, and that is definitly intentional forwardRef argument type one github :

     interface ForwardRefRenderFunction<T, P = {}> {
         (props: P, ref: ForwardedRef<T>): ReactElement | null;
         displayName?: string | undefined;
         // explicit rejected with `never` required due to
         // https://github.com/microsoft/TypeScript/issues/36826
         /**
          * defaultProps are not supported on render functions
          */
         defaultProps?: never | undefined;
         /**
          * propTypes are not supported on render functions
          */
         propTypes?: never | undefined;
     }
    
  2. The return type of React.forwardRef has a default prop field suggesting that it is intended to be typed correctly when used in jsx forwardRef type and return type on github :

     interface ForwardRefExoticComponent<P> extends NamedExoticComponent<P> {
         defaultProps?: Partial<P> | undefined;
         propTypes?: WeakValidationMap<P> | undefined;
     }
    
     function forwardRef<T, P = {}>(render: ForwardRefRenderFunction<T, P>): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;
    
  3. The typescript documentation explicitly states that they added support for prop type transformation on the library side in order to allow react defaultProp to type correctly From typescript 3 whats new: 'support for defaultProps in JSX':

TypeScript 3.0 adds support for a new type alias in the JSX namespace called LibraryManagedAttributes. This helper type defines a transformation on the component’s Props type, before using to check a JSX expression targeting it; thus allowing customization like: how conflicts between provided props and inferred props are handled, how inferences are mapped, how optionality is handled, and how inferences from differing places should be combined.

However the page also mentions the following for functional components :

For function components (formerly known as SFCs) use ES2015 default initializers:

function Greet({ name = "world" }: Props) {
  return <div>Hello {name.toUpperCase()}!</div>;
}

But let's not stop there. What if we really want to use defaultProp ? I need to know why it doesn't work.

  1. Searching for LibraryManagedAttributed in @types/react gives only one hit: Code from github:

     type LibraryManagedAttributes<C, P> = C extends React.MemoExoticComponent<infer T> | React.LazyExoticComponent<infer T>
         ? T extends React.MemoExoticComponent<infer U> | React.LazyExoticComponent<infer U>
             ? ReactManagedAttributes<U, P>
             : ReactManagedAttributes<T, P>
         : ReactManagedAttributes<C, P>;
    

Is this it ? I won't pretend to fully understand this last piece of code, but there is no mention of ForwardRefExoticComponent there. Is this a bug ? Could it work if something were to be added in this type definition ? Should I open an issue ?

Let me know what you guys think !

Sign up to request clarification or add additional context in comments.

Comments

2

Try:

export interface MessageBoxProps extends React.AllHTMLAttributes<HTMLDivElement>
{
    test?: string;
}

It worked for me. Now you can set children, href, onClick and other attributes as well.

Here is forked sandbox link : https://codesandbox.io/s/forwardref-with-typescript-forked-sn0u3?file=/src/App.tsx

1 Comment

I'm not sure about your answer to this question, but it certainly helped me work out my own issue, thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.