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.
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;
}
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>>;
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.
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 !