skip to Main Content

I want to create a packages like react-bootstrap.

<Card>

<Card.Title>Hello</Card.Title>

</Card>

I wish to create like below. But I couldn’t able to do.

<MyCustomComponent>
<MyCustomComponent.Header>
Header goes here
</MyCustomComponent.Header>
</MyCustomComponent>


interface Props {
 children?: React.ReactNode | React.ReactNode[]
}

export const MyCustomComponent:FC<Props> = () => { 

}

Props throwing error like .Header does not exist on type FC<Props & { children?: ReactNode; }>

React Version: ^18

3

Answers


  1. Check React-bootstrap as reference of your code

    Below code is from react-bootstrap’s form component :

    export default Object.assign(Form, {
      Group: FormGroup,
      Control: FormControl,
      Floating: FormFloating,
      Check: FormCheck,
      Switch,
      Label: FormLabel,
      Text: FormText,
      Range: FormRange,
      Select: FormSelect,
      FloatingLabel,
    });
    

    As you see, React-bootstrap is using Object.assign function to assign differential components to Form Component.

    So, in your case code should be:

    export default Object.assign(MyCustomComponent, {
      Header: MyCustomComponentHeader,
    });
    

    Check MDN’s Object.assign() document for more.

    Login or Signup to reply.
  2. As you know in React > 18 children props was deleted from React.FC and you are able to use PropsWithChildren type (import it from React) like this:

    const MyComponent: React.FC<PropsWithChildren<Props>> => …
    

    And then to achieve the approach you want, you can follow like this:

    const Header: React.FC = () => …
    const Content: React.FC = () => …
    
    const MyCustomComponent = {Header, Content};
    export default MyCustomComponent;
    

    And in other components, you can use it like this:

    import MyCustomComponent …
    …
    <MyCustomComponent.Header />
    <MyCustomComponent.Content />
    
    Login or Signup to reply.
  3. I would make MyCustomComponent require all of it’s children as props, and add them all to the MyCustomComponent as a property, like so:

    // MyCustomComponent.tsx
    
    import { ReactNode } from 'react';
    import Header from './MyCustomComponentHeading';
    
    type Props = {
      header: ReactNode;
    };
    
    const MyCustomComponent = ({ header }: Props) => {
      return (
        <>
          <h1>My Custom Component</h1>
          {header}
        </>
      );
    };
    
    // this is the magical step. Add as many components as you want like this.
    MyCustomComponent.Header = Header;
    
    export default MyCustomComponent;
    
    // MyCustomComponentHeading.tsx
    
    type Props = {
      heading: string;
    };
    
    const Header = ({ heading }: Props) => {
      return <h2>{heading}</h2>;
    };
    
    export default Header;
    

    It would then be consumed like this:

    // App.tsx
    
    import MyCustomComponent from '../library/MyCustomComponent';
    
    
    export const App = () => {
      return (
        <>
          <MyCustomComponent
            header={<MyCustomComponent.Header heading="My Custom Heading" />}
          />
        </>
      );
    };
    
    

    Suggested improvement – use the Context API

    I would additionally recommend using the Context API to ensure that these children components are only rendered within their parent.

    For a full working example that includes a custom context, check out this stackblitz project.

    Youtube Video

    This answer has been strongly guided by this YouTube video which I highly recommend giving a watch, to properly understand how the custom context can be utilised.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search