Search code examples
reactjstypescriptreact-typescript

Enforce Plain HTML Element Passed as Children in Typescript


I'm trying to create a React component which enforces that its children are plain HTML elements (such as <p>...</p>) and not React components (such as <SomeComponent/>)

Here's some code I've tried, to no avail

import React from "react"

interface ITooltipProps {
  // Goal is to require a plain HTML child here, i.e. <p>...</p>, and error if we get anything else
  // children: React.ReactElement<JSX.IntrinsicElements[keyof JSX.IntrinsicElements]>;

  // This doesn't work because the JSX.IntrinsicElements map just returns props, not the element type
  // children: JSX.IntrinsicElements[keyof JSX.IntrinsicElements];
  
  // This doesn't work because it's just asking for a react component that can take props of an HTML element (all of which are optional)
  children: React.ReactElement<JSX.IntrinsicElements[keyof JSX.IntrinsicElements]>;
}

export function Tooltip({
  children
}: ITooltipProps) {
  return (
    <div>
        {children}
    </div>
  )
}

// An element which doesn't return a plain HTML child
const F = () => {
  return (
    <>
        <p>Hello</p>
        <p>World</p>
    </>
  )
}

const x = (
    // Expect <F/> to error, since F isn't returning a plain HTML object / is returning a fragment
  <Tooltip>
    <F/>
  </Tooltip>
)

(Here's a link to a TS playground with the above code)

Am wondering if anybody knows how to enforce that a component's children are "primitive" HTML elements, as opposed to react components – thanks!


Solution

  • This is not possible.

    Rendered JSX has a return type of JSX.Element no matter what it contains. So plain html, a fragment, or a rendered component all have the same type. Which means you can't use types to constrain it in this way.

    See this playground