[@types/react] Enable passing children down to "ExoticComponent"s#33602
[@types/react] Enable passing children down to "ExoticComponent"s#33602raon0211 wants to merge 2 commits intoDefinitelyTyped:masterfrom
Conversation
|
@raon0211 Thank you for submitting this PR! 🔔 @johnnyreilly @bbenezech @pzavolinsky @digiguru @ericanderson @tkrotoff @DovydasNavickas @onigoetz @theruther4d @guilhermehubner @ferdaber @jrakotoharisoa @pascaloliv @Hotell @franklixuefei @Jessidhia @pshrmn @threepointone - please review this PR in the next few days. Be sure to explicitly select If no reviewer appears after a week, a DefinitelyTyped maintainer will review the PR instead. |
|
@raon0211 The Travis CI build failed! Please review the logs for more information. Once you've pushed the fixes, the build will automatically re-run. Thanks! |
|
This is a bug with FunctionComponent, not with ExoticComponent. It's only kept like that on FunctionComponent for backwards compatibility. |
|
I got it. But then I think the type definition below should be fixed to return function memo<P extends object>(
Component: SFC<P>,
propsAreEqual?: (prevProps: Readonly<PropsWithChildren<P>>, nextProps: Readonly<PropsWithChildren<P>>) => boolean
): NamedExoticComponent<P>;This will only affect the behavior of |
|
I'm also running in this issue when trying to pass Is there a known workaround? |
|
This issue also effects usage of import * as React from 'react';
type Props = {};
declare class C extends React.Component<Props> {}
type ComponentInstance = InstanceType<typeof C>;
const ForwardedRef = React.forwardRef<ComponentInstance, Props>(
(props, ref) => <C {...props} ref={ref} />,
);
/* Type '{ children: string; }' has no properties in common with type 'IntrinsicAttributes & RefAttributes<C>' */
<ForwardedRef>foo</ForwardedRef>;@raon0211 Would you mind also adding a failing test for this? |
|
I'm able to workaround the issue with import * as React from 'react';
type Props = {};
declare class C extends React.Component<Props> {}
const Memoized = React.memo(C) as React.ComponentType<Props>;
<Memoized>foo</Memoized>;However I have no such workaround for |
|
The memoized component's props interface should have |
|
I agree with that it is better to set The type definition of function memo<P extends object>(
Component: SFC<P>,
propsAreEqual?: (prevProps: Readonly<PropsWithChildren<P>>, nextProps: Readonly<PropsWithChildren<P>>) => boolean
): NamedExoticComponent<P>; // Why not PropsWithChildren<P> here? |
|
@ferdaber Thanks for your help here. Can you clarify whether you're suggesting to make other (different) changes to the typings in order to fix this, or whether this is a "won't fix" and is instead for users to fix by explicitly specifying |
|
@OliverJAsh You may modify the current type definition by declaration merging to fix the problem temporarily. For example I am using the following definition file in my project to fix the problem for declare namespace React {
function memo<Props extends object>(
Component: SFC<Props>,
propsAreEqual?: (
prevProps: Readonly<PropsWithChildren<Props>>,
nextProps: Readonly<PropsWithChildren<Props>>
) => boolean
): NamedExoticComponent<PropsWithChildren<Props>>;
}I will add the test for |
|
@OliverJAsh I am mostly advocating for the latter: i.e. userland should be explicitly specifying |
|
@ferdaber So it may be that the inconsistency is intended to make users specify |
|
Kind of... it's more that we just don't want to add to the cruft. Removing the injected children right now will cause lots of breaking changes, and this package (like everything in DT) does not follow semver so that's not a good idea. We also don't want to inject here too so that it's even more breaking changes to have to go through when we eventually update everything. |
|
@ferdaber Thanks for explaining :-) May I ask why we want to avoid injecting children? Why is this not sound? |
|
I understood. Thanks for your kind explanation, I should rush to add |
|
@OliverJAsh I guess it's because we can type-check much strictly by being able to distinguish components that have children and those do not. I was also always wondering why we could pass children down to those that don't specify |
|
@raon0211 is correct. It's not sound to auto-inject children because some components simply do not take children. Not only that, but the auto-injected children is a whole mess of unioned types that basically allows anything, but some components have render props children, some take objects, some take arrays, etc. so enforcing that the user actually properly type their children like any other prop is more sound. |
|
I will close the pull request. Thanks again, I learned some today. Couldn’t think of the render props. |
|
Why PR is closed? |
|
This issue is resolved because you can add |
|
@jacobrask does that mean that at some point React will stop injecting The issue arises when connecting a functional component with a Redux store using TS. Right now the latest version of const component:React.FC<{whatever?:string}> =
({whatever, children})=>(<div>{children}</div>);
const ConnectedComponent=connect(mapStateToProps)(T);
[...]
return <ConnectedComponent>Any content</ConnectedComponent>; // this causes a typescript compiler error
|
The definition of
ExoticComponent<P>is as follows:(please refer to the introduction of
ExoticComponentto #30054)So it is not possible to pass children down to
ExoticComponents using JSX unless the component specifies thechildrenproperty in props. However, the behavior ofFunctionComponents is quite different.The props of
FunctionComponents automatically contains the optionalchildrenproperty. Hence it is able to pass down children to FCs with no regard of whether thechildrenproperty is specified in the component or not. Similar is for class-based components.It is more confusing when first making a
React.FunctionComponentand thenmemoing it:(TypeScript 3.3.3333)
Note that the compiler complains that we cannot pass children down to the memoed component, while we can still pass it down to the original component.
This PR fixes this confusing behavior by using
PropsWithChildreninExoticComponent, which is the same way already applied toFunctionComponent.npm test.)npm run lint package-name(ortscif notslint.jsonis present).Select one of these and delete the others:
If changing an existing definition:
tslint.jsoncontaining{ "extends": "dtslint/dt.json" }.