Source: app/util/proptypes/childrenOfType.js

import { Children } from 'react';

/** @module */

const childrenOfType = components => {
  const wrappedTypeChecker = isRequired => {
    const displayNames = components.map(component => component.displayName);
    const names = components.map(component => component.name);

    return (props, propName, componentName) => {
      if (isRequired) {
        if (props[propName] === null || typeof props[propName] === 'undefined') {
          return new Error(
            `The '${propName}' is marked as required in ${componentName} but its value is null or undefined`,
          );
        }
      }

      const children = Children.toArray(props[propName]);

      for (let i = 0; i < children.length; i++) {
        const child = children[i];

        if (
          !components.includes(child.type) &&
          !displayNames.includes(child.type.displayName) &&
          !names.includes(child.type.name)
        ) {
          const componentNames = JSON.stringify(
            components.map(comp => comp.displayName || comp.name),
          );
          return new Error(
            `Expected ${propName} of ${componentName} to be one of ${componentNames} but got type "${child.type &&
              (child.type.displayName || child.type.name)}" instead`,
          );
        }
      }

      return null;
    };
  };

  const typeChecker = wrappedTypeChecker(false);
  typeChecker.isRequired = wrappedTypeChecker(true);
  return typeChecker;
};

/**
 * Returns a propType checker that checks if the passed children are of one of the provided
 * component types.
 * @function childrenOfType
 * @param {Array} components An array of React components which are allowed
 * @example MyComponent.propTypes = {
 *   children: childrenOfType([SomeComponent, SomeOtherComponent]),
 * }
 * @category templating
 */
export default childrenOfType;