1

I'm trying to implement a kind of React children components validation to have only specific components but facing an issue with type.

Please check out my code below:

// a component needs the validation

import { Children, FunctionComponent, isValidElement } from 'react';
import { AccordionProps } from '@components/old/signup/organisms/accordion/interfaces';
import CheckFormField from '@components/old/signup/organisms/checkFormField';
import AccordionHeader from '@components/old/signup/organisms/accordionHeader';


const Accordion: FunctionComponent<AccordionProps> = ({ children }) => {
                                                                                             
const allowedComponents = [CheckFormField, AccordionHeader]; // these can only be children

Children.map(children, (child) => {
    const childType = isValidElement(child) && child.type;
    if (!allowedComponents.includes(childType)) { // here comes type error to use includes method
      throw new Error('Error message..');
    }
  });
                            :
                            :
return (
  <div>{children}</div>
);

The type error from above code:

TS2345: Argument of type 'string | false | JSXElementConstructor<any>' is not assignable to parameter of type 'FunctionComponent<CheckFormFiledProps> | FunctionComponent<AccordionHeaderProps>'.   Type 'string' is not assignable to type 'FunctionComponent<CheckFormFiledProps> | FunctionComponent<AccordionHeaderProps>'.

and the props that I defined below:

// interfaces.ts

export interface AccordionProps {
  children: ReactElement<any> | ReactElement[];
}

I suspect that the children prop above isn't probably proper type to make it but no luck to search it so far.

references:

only allow children of a specific type in a react component

Only allow specific components as children in React and Typescript

Thanks in advance.

footlessbird
  • 303
  • 2
  • 11

2 Answers2

0

As a possible solution to this is to pass the target children components to the parent component via component properties, instead of classic children. Let's call the child components property Components, to distinguish from the standard children.

So here is how the child component array is validated (by the TypeScript itself) and rendered via the Main component:

// Main.tsx

import ValidChildOne from './ValidChildOne';
import ValidChildTwo from './ValidChildTwo';

// declaring what is allowed as children
type AllowedChild = typeof ValidChildOne | typeof ValidChildTwo;

interface MainProps {
  Components: AllowedChild[];  // array
}

export default function Main({ Components }: MainProps) {
  return (
    <>
      <h1>Main</h1>
      {
        Components.map((Child, index) => <Child key={index} />)
      }
    </>
  );
}

Respectively, here is how the Main component (and its props) is applied:

// App.tsx

import Main from './Main';
import ValidChildOne from './ValidChildOne';
import ValidChildTwo from './ValidChildTwo';

export default function App() {
  return (
    <Main Components={[ValidChildOne, ValidChildTwo]} />
  );
}

Finally, the child components' code for reference:

// ValidChildOne.tsx

export default function ValidChildOne() {
  return <h2>ValidChildOne</h2>;
}
// ValidChildTwo.tsx

export default function ValidChildTwo() {
  return <h2>ValidChildTwo</h2>;
}
  • Good but not that I am looking for as ultimately I wanted to make it as utility function to apply it for components which need validation. – footlessbird Aug 06 '21 at 12:30
0

To answer myself the code below with no interface change makes it work.

import {Children, FunctionComponent, isValidElement} from 'react'
                                 :
                                 :
Children.map(children, (child) => {
    const childType = isValidElement(child) && child.type;
    if (!allowedComponents.includes(childType) as FunctionComponent<any>) { // as FunctionComponent<any> shuts the type error complaining
      throw new Error('Error message..');
    }
  });
footlessbird
  • 303
  • 2
  • 11