TypeScript and JSX don't understand cases when I want to set specific props

Viewed 256

I would to like to set specific props in a component and I have the types for this, but compiler doesn't show me an error when I'm trying to set wrong props using JSX. I get an error only when I use React.createElement but I don't see the TypeError using JSX.

For instance, We have two types for two props of components

type T1 = {
    a: boolean
}

type T2 = {
    b: string;
    c: number;
}

And We have two components with the specific props

var MyComponenWithT1: React.FC<T1> = ({ a }) => <div>{a ? 1: 2}</div>;
var MyComponenWithT2: React.FC<T2> = ({ b }) => <div>{b ? 1: 2}</div>;

I try to define two elements

var GoodElement: React.ReactElement<T1> = React.createElement(MyComponenWithT1, { a: true })
var WrongElement: React.ReactElement<T1> = React.createElement(MyComponenWithT2, {b: 'f', c: 5})

It works good. We see that compiler gives back us an error when We try to define WrongElement.

In this example We use JSX without React.createElement and compiller doesn't see any errors

var GoodJSXElement: React.ReactElement<T1> = <MyComponenWithT1 a={true} />
var WrongJSXElement: React.ReactElement<T1> = <MyComponenWithT2 b='f' c={5} />

Now, compiler shows us that everything is correct, but it's actually wrong. MyComponenWithT2 has props with type T2, and it can't be assigned to an variable which waits the element with T1 props. Actually, We have both equal cases one case use CreateElement method, and other use JSX. But We have the error in one and don't another. Why? And How can I say compiler to show me the error if I try to assign to the variable an element with incorrect props

TS PlayGround

1 Answers

The typescript definition of React.FC is that it a function which returns a ReactElement where the generics for props and type are both any (or it returns null). Once the function has been called to generate some HTML code, that returned code is not aware of the props which were used to create it.

type FC<P = {}> = FunctionComponent<P>;

interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
    propTypes?: WeakValidationMap<P>;
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

When you use JSX you are basically just calling the function. If you remove the types from your variables, you'll see that both GoodJSXElement and WrongJSXElement have the type JSX.Element.

React.createElement has a different return type. Here's one of the overloads:

function createElement<P extends {}>(
    type: FunctionComponent<P> | ComponentClass<P> | string,
    props?: Attributes & P | null,
    ...children: ReactNode[]): ReactElement<P>;

You'll notice that it returns ReactElement<P> instead of the ReactElement<any> that you would get from calling the function component directly. So that's why there's a difference.

An element is basically the resolved HTML. If it doesn't care how it was created, I question why you care. I suspect your design can be rewritten in a way where instead of passing around the ReactElement, you pass around the ComponentType instead.

Related