lpetrucci
lpetrucci

Reputation: 1677

React + Typescript: Expecting children component with specific props

I'm building a component that requires at least two specific child components to be used.

These two components are exported as dot notations from the main component and include defaultProps to identify them in the main component:

export interface ComponentPropsInterface {
  children: React.ReactChild;
}

const Component = ({ children }: ComponentPropsInterface ): JSX.Element => {
  const childrenArray = React.Children.toArray(children);
  return (
    <>
        {
          // Find a component with the wanted prop and place it in a specific place
          childrenArray.filter(
            (child) =>
              child.props.__TYPE === 'Trigger'
          )[0]
        }
    </>
  );
};

const Trigger = ({
  children,
}: {
  children: React.ReactChild;
  __TYPE?: string;
}) => <>{children}</>;

// Prop is assigned here automatically
Trigger.defaultProps = {
  __TYPE: 'Trigger',
};

// Export as dot notation
Popover.Trigger = Trigger;

// Export component
export default Component;

In the above I'm checking if Component has children, which is okay, but it's not failproof.

I would like to ensure children has a at least one child with the __TYPE === 'Trigger' prop.

Is there any way to do this directly from Typescript? Or should I throw an error if the child component I need isn't present?

EDIT: For precision's sake, I'd like the error to appear in the IDE on the parent <Component/> when it is used without one of the required children.

Upvotes: 1

Views: 2249

Answers (1)

As a rule of thumb, if you are using typescript and you want to use Array.prototype.filter - use custom typeguard as a filter predicate:

import React from 'react'

export interface ComponentPropsInterface {
  children: React.ReactElement<{ __TYPE: string }>[];
}

const predicate = (child: React.ReactChild | React.ReactFragment | React.ReactPortal):
  child is React.ReactElement<{ __TYPE: 'Trigger' }> =>
  React.isValidElement(child) ? child.props.__TYPE === 'Trigger' : false

const Component = ({ children }: ComponentPropsInterface): JSX.Element => (
  <>
    {
      // Find a component with the wanted prop and place it in a specific place
      React.Children.toArray(children).filter(predicate)[0]
    }
  </>
)

const Trigger = (props: {
  __TYPE: string;
}) => <div></div>;

const Invalid: React.FC<{ label: string }> = ({ label }) => {
  return <div>{label}</div>;
};


const Test = () =>
  React.createElement(Component, {
    children: [
      React.createElement(Invalid, { label: "d" }), // error
      React.createElement(Trigger, { __TYPE: 'Trigger' }), // ok
    ],
  })

Playground Also, in order to get props you need to check if element is valid React.isValidElement

AFAIK, it is possible to infer type of child components only with native react syntax createElement, because this method has a lot of overloads. jsx syntax, from the other side, infers every element to JSX.Element

Please see my article

Upvotes: 3

Related Questions