2

Say you have a component tree like:

 function Parent(props: { componentProp: ReactElement }) {
   return (
     <>
       {props.componentProp}
     </>
   );
 }

 function ChildA(props: { message: string }) { return (<h1>{props.message}</h1>) }
 function ChildB(props: { message: string }) { return (<h2>{props.message}</h2>) }

 function App() {
   return (
     <Parent componentProp={<ChildA message="hi"/>}
   );
 }

As written, you could replace ChildA with ChildB in componentProp and Typescript would not complain.

Is it possible to restrict the type of componentProp such that you could only pass ChildA, and passing ChildB would throw a type error?

fafcrumb
  • 333
  • 3
  • 13
  • At first glance this didn't look like quite the same question, but looking again yes it does show that you can't strictly type a rendered element. – fafcrumb Jan 05 '22 at 17:06

1 Answers1

1

The type of any rendered JSX is always the same -- I don't know of any way to distinguish them. Even if you do something like this, the rendered JSX type is unaffected by the function return type:

type Valid = ReactElement & { __valid: true }
function ChildA(props: { message: string }): Valid {
    const result = <h1>{props.message}</h1>
    return { ...result, __valid: true } // fails
}
const ca = <ChildA message="foo"/> // still ReactElement

However, you can distinguish the component functions themselves, and pass them as props instead of their rendered versions. If the valid components have different props, then you can ride off that, otherwise you could add either a junk prop or a junk return object property:

export default null
type Valid = (props: unknown) => ReactElement & { _valid: null }

// option 1:
const A = () => {
    const result = <></>
    return { ...result, _valid: null }
}

// option 2:
const B = (() => <></>) as unknown as Valid

const C = () => <></>

function Parent({ Child }: { Child: Valid }) {
    return <><Child /></>
}

function App() {
    return (
        <>
            <Parent Child={A} /> {/*passes*/}
            <Parent Child={B} /> {/*passes*/}
            <Parent Child={C} /> {/*fails*/}
        </>
    )
}
Luke Miles
  • 941
  • 9
  • 19
  • Thanks for this! Hard to justify creating a worse API just for type validation. I thought I must have been missing something obvious but I suppose not. – fafcrumb Jan 05 '22 at 17:08