Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Component children typecheck with typescript

Here is the scenario:

I have a custom component:

class MyComponent extends React.Component {
  render () {
    return (
      <SuperComponent>
        <SubComponent1 />  // <- valid child
      </SuperComponent>
    )
}

class MyComponent extends React.Component {
  render () {
    return (
      <SuperComponent>
        <SubComponent2 />  // <- No! It's not right shape
      </SuperComponent>
    )
}

and the referenced SuperComponent and SubComponent1 are:

interface superPropsType = {
  children: ReactElement<subPropsType1>
}
class SuperComponent extends React.Component<superPropsType> { ... }


interface subPropsType1 = {
  name: string
}
class SubComponent1 extends React.Component<subPropsType1> { ... }


interface subPropsType2 = {
  title: string
}
class SubComponent2 extends React.Component<subPropsType2> { ... }

I want SubComponent1 to be the only valid child of SuperComponent, that is, I wish typescript can throw an error if I place <SubComponent2 /> or Other types as child of <SuperComponent>

It seems like typescript only check that the child of should have the type of ReactElement, but ts doesn't check the shape of props of that child (which is subPropsType1), that is, if I place a string or number as child of SuperComponent, ts will complaints that type requirement doesn't meet, but if I place any jsx tag here(which will transpiled to ReactElement), ts will keep silent

Any idea ? And if any configs are required to post here, please don't hesitate to ask

Really appreciate any idea and solution

like image 290
zmou-d Avatar asked Oct 10 '18 03:10

zmou-d


People also ask

How do you pass children in React TypeScript?

By invoking them between the opening and closing tags of a JSX element, you can use React children for entering data into a component. The React children prop is an important concept for creating reusable components because it allows components to be constructed together.

What is ReactNode TypeScript?

The ReactFragment , which is included in the ReactNode type, includes an empty interface. Due to the way that TypeScript handles excess property checks, this means that the ReactNode type will accept any object except an object literal. For almost all intents and purposes, it is functionally equivalent to an any type.


2 Answers

As of TypeScript 3.1, all JSX elements are hard-coded to have the JSX.Element type, so there's no way to accept certain JSX elements and not others. If you wanted that kind of checking, you would have to give up the JSX syntax, define your own element factory function that wraps React.createElement but returns different element types for different component types, and write calls to that factory function manually.

There is an open suggestion, which might be implemented as soon as TypeScript 3.2 (to be released in late November 2018), for TypeScript to assign types to JSX elements based on the actual return type of the factory function for the given component type. If that gets implemented, you'll be able to define your own factory function that wraps React.createElement and specify it with the jsxFactory compiler option, and you'll get the additional type information. Or maybe @types/react will even change so that React.createElement provides richer type information, if that can be done without harmful consequences to projects that don't care about the functionality; we'll have to wait and see.

like image 172
Matt McCutchen Avatar answered Oct 07 '22 23:10

Matt McCutchen


I would probably declare SuperPropsType.children as:

children: React.ReactElement<SubPropsType1> | React.ReactElement<SubPropsType1>[];

To account for the possibility of having both a single and multiple children.

In any case, as pointed out already, that won't work as expected.

What you could do instead is declare a prop, let's say subComponentProps: SubPropsType1[], to pass down the props you need to create those SubComponent1s, rather than their JSX, and render them inside SuperComponent:

interface SuperPropsType {
  children?: never;
  subComponentProps?: SubPropsType1[];
}

...

const SuperComponent: React.FC<SuperPropsType> = ({ subComponentProps }) => {
  return (
    ...

    { subComponentProps.map(props => <SubComponent1 key={ ... } { ...props } />) }

    ...
  );
};
like image 26
Danziger Avatar answered Oct 08 '22 01:10

Danziger