23

I run into a trouble with trying to allow only specific children to be passed to a component.

Here are my components, HeaderLink

import { SFC, ReactElement } from "react";

import "./Header.scss";

export interface IHeaderLinkProps {
  url: string;
  label: string;
}

const HeaderLink: SFC<IHeaderLinkProps> = ({ url, label }): ReactElement => (
  <li className="nav-item">
    <a href={url}>{label}</a>
  </li>
);

export default HeaderLink;

and Header

import { SFC, ReactElement } from "react";
import { IHeaderLinkProps } from "./HeaderLink";

import "./Header.scss";

interface IHeaderProps {
  children: ReactElement<IHeaderLinkProps> | ReactElement<IHeaderLinkProps>[];
}

const Header: SFC<IHeaderProps> = ({ children }): ReactElement => {
  return (
    <header className="header">
      <div className="container">
        <div className="row">
          <div className="col">
            <img
              className="header__logo"
              src={"/logo.png"}
              alt="monqrime-logo"
            />

            <ul className="header__nav">{children}</ul>
          </div>
        </div>
      </div>
    </header>
  );
};

So the problem is that I want to allow only HeaderLink components to be passed as children to the Header, but my current solution still allows me to put everything as a children.

JSEvgeny
  • 2,550
  • 1
  • 24
  • 38

2 Answers2

17

I would probably declare IHeaderProps.children as:

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

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

In any case, what you want is not possible. See:

What you could do instead is declare a prop, let's say links?: IHeaderLinkProps[], to pass down the props you need to create those HeaderLinks, rather than their JSX, and render them inside Header:

interface IHeaderProps {
  children?: never;
  links?: IHeaderLinkProps[];
}

...

const Header: React.FC<IHeaderProps> = ({ links }) => {
  return (
    ...

    <ul className="header__nav">
      { links.map(link => <HeaderLink key={ link.url } { ...link } />) }
    </ul>

    ...
  );
};
Danziger
  • 19,628
  • 4
  • 53
  • 83
  • 2
    I appreciate your help @Danziger, but unfortunately you first solution doesn't work for me instead it throws `Type 'Element[]' is not assignable to type 'ComponentType[]'. Type 'Element' is not assignable to type 'ComponentType'.` Your second solution would work for sure and it was my first implementation, but I just wanted to make it look more fancy :D – JSEvgeny Dec 17 '19 at 20:33
  • @JSEvgeny My bad, that should be `React.ReactElement`, not `React.ComponentType`. In any case, even though that might look like the right way to do it, it won't work either (as shown in those other 2 questions), but it will not show an error. Your best option is to make that configuration-driven, which is what I'm suggesting in the second part of the answer. – Danziger Dec 17 '19 at 20:38
  • 1
    Yes I came up with what I got in question before I found out it will not work from the answers you sent :( I'd still hope for new possible solutions to come, but if there will not be any I'd go with the basics and accept your answer – JSEvgeny Dec 17 '19 at 20:45
  • 1
    I will go with passing HeaderLinkProps typed object array as a property since no more suggestions are coming :) – JSEvgeny Dec 18 '19 at 14:24
  • I used this pattern and got the feedback that it was "too imperative" which is how I ended up on this SO question in the first place. oof – Arajay Apr 05 '22 at 21:40
-1

I'm doing this (based on Danziger's answer):

type MaybeArray<T> = T[] | T
export type ChildrenOfType<T> = MaybeArray<ReactElement<ComponentPropsWithoutRef<T>>>

Usage:

interface SelectBoxProps extends Pick<React.ComponentPropsWithoutRef<'select'>, 'id' | 'value' | 'onChange' | 'disabled' | 'readOnly' | 'onFocus' | 'onBlur' | 'required' | 'defaultValue' | 'style'> {
    children?: ChildrenOfType<'option'>
}

I don't know if it actually does anything though -- my IDE doesn't complain when I throw a <div> in there but at least it serves as human-documentation.

mpen
  • 272,448
  • 266
  • 850
  • 1,236