Reputation: 1677
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
Reputation: 33041
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