I’m trying to find a way to support typings for cross-platform React Native components in a single file using already supported TS features.
In this example I use a functional React component, but the same applies to a class-based component.
TypeScript Version:
Nightly
Search Terms:
type-guard function parameter narrowing
Code
import React from "react"
interface CommonProps {
commonProp: 42;
}
interface IOSProps extends CommonProps {
iOSProp: 42;
__platform_ios?: never
}
interface AndroidProps extends CommonProps {
androidProp: 42;
__platform_android?: never
}
function SomeComponent(props: IOSProps | AndroidProps) {
return <div/>
}
type ExtractIOS<C> = C extends React.ComponentType<infer R>
? R extends { __platform_ios?: never }
? React.ComponentType<Extract<R, { __platform_ios?: never }>>
: never
: never
function onIOS<C extends React.ComponentType<any>>(
component: C
): component is ExtractIOS<C> {
return true // In reality, this would check if the current React Native platform is iOS.
}
if (onIOS(SomeComponent)) {
(<SomeComponent commonProp={42} iOSProp={42} />);
// We want this to fail
(<SomeComponent commonProp={42} androidProp={42} />);
}
Expected behavior:
For it to work similarly when using re-assignment instead of a type-guard:
function makeIOS<C extends React.ComponentType<any>>(
component: C
): ExtractIOS<C> {
return component as any
}
const IOSComponent = makeIOS(SomeComponent);
(<IOSComponent commonProp={42} iOSProp={42} />);
// This fails as expected
(<IOSComponent commonProp={42} androidProp={42} />);
Actual behavior:
Type of SomeComponent remains function SomeComponent(props: IOSProps | AndroidProps): JSX.Element after applying the type-guard.
Playground Link:
This one's a little tricky. The problem is that because parameters are compared contravariantly, you're trying to narrow down a subtype (which is "narrow") to a supertype (which is "wide"). Narrowing is supposed to make types more capable, so but narrowing SomeComponent from (x: IOSProps | AndroidProps) => Element to (x: IOSProps) => Element is strictly making it take fewer types of values.
I think you want SomeComponent itself to be a union, not just the props: playground
Ah yes, nice one 👌 Thanks, this solves the actual problem I had, so this can be closed as far as I am concerned.