I'm trying to understand if there is a limitation to type inference with using tsx files in typescript.
If I create a stateless react component:
interface TestProps {
foo: string;
}
export const TestComp: React.StatelessComponent<TestProps> = x => {
return(<div>{foo}</div>);
};
and then in a second tsx file try the following:
import { TestComp } from './TestComp';
const getProperties = function<P>(node: React.ReactElement<P>) : P {
return node.props
};
var props1 = getProperties(React.createElement(TestComp, { foo : 'bar' }));
var props2 = getProperties(<TestComp foo='bar' />);
props1 will have an inferred type of TestProps, props2 will have an inferred type of any.
I was under the impression that the two last lines were equivalent. Is there a reason that Typescript believes object in the second call to be a React.ReactElement<any>
?
Michael's answer pointed me in the right direction because it got me digging into the .d.ts files for React, but the actual reason is more subtle.
This block of code
export const TestComp: React.StatelessComponent<TestProps> = x => {
return(<div>{foo}</div>);
};
Using the interface defined as in the react .d.ts as:
interface StatelessComponent<P = {}> {
(props: P & { children?: ReactNode }, context?: any): ReactElement<any> | null;
...
}
This shows that x
will have an inferred type of TestProps & { children?: ReactNode }
. This works fine and can be seen in the intellisense results from VS Code.
However, the return type of that function isn't ReactElement<TestProps>
as I expected, it is ReactElement<any>
.
By comparison createElement is defined as:
function createElement<P>(
type: SFC<P>,
props?: Attributes & P,
...children: ReactNode[]): SFCElement<P>;
where
type SFC<P = {}> = StatelessComponent<P>;
interface SFCElement<P> extends ReactElement<P> { }
Calling React.createElement(TestComp, { foo : 'bar' })
allows the generic parameter of TestProps
from TestComp
to bubble all the way up into the final created element.
To summarise: By explicitly defining TestComp
as a StatelessComponent<TestProps>
I have also defined the returned element from the factory function as a ReactElement<any>
. Removing this definition and writing the component as:
export function TestComp(x: TestProps) {
...
}
or
export const TestComp = (x: TestProps) => {
....
}
Gives Typescript less information and allows it to infer the correct type rather than forcing it to use the wrong one...