only allow children of a specific type in a react component

ValidationReactjs

Validation Problem Overview


I have a Card component and a CardGroup component, and I'd like to throw an error when CardGroup has children that aren't Card components. Is this possible, or am I trying to solve the wrong problem?

Validation Solutions


Solution 1 - Validation

For React 0.14+ and using ES6 classes, the solution will look like:

class CardGroup extends Component {
  render() {
    return (
      <div>{this.props.children}</div>
    )
  }
}
CardGroup.propTypes = {
  children: function (props, propName, componentName) {
    const prop = props[propName]

    let error = null
    React.Children.forEach(prop, function (child) {
      if (child.type !== Card) {
        error = new Error('`' + componentName + '` children should be of type `Card`.');
      }
    })
    return error
  }
}

Solution 2 - Validation

You can use the displayName for each child, accessed via type:

for (child in this.props.children){
  if (this.props.children[child].type.displayName != 'Card'){
    console.log("Warning CardGroup has children that aren't Card components");
  }  
}

Solution 3 - Validation

You can use a custom propType function to validate children, since children are just props. I also wrote an article on this, if you want more details.

var CardGroup = React.createClass({
  propTypes: {
    children: function (props, propName, componentName) {
      var error;
      var prop = props[propName];

      React.Children.forEach(prop, function (child) {
        if (child.type.displayName !== 'Card') {
          error = new Error(
            '`' + componentName + '` only accepts children of type `Card`.'
          );
        }
      });

      return error;
    }
  },
  
  render: function () {
    return (
      <div>{this.props.children}</div>
    );
  }
});

Solution 4 - Validation

Use the React.Children.forEach method to iterate over the children and use the name property to check the type:

React.Children.forEach(this.props.children, (child) => {
    if (child.type.name !== Card.name) {
        console.error("Only card components allowed as children.");
    }
}

I recommend to use Card.name instead of 'Card' string for better maintenance and stability in respect to uglify.

See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name

Solution 5 - Validation

For those using a TypeScript version. You can filter/modify components like this:

this.modifiedChildren = React.Children.map(children, child => {
            if (React.isValidElement(child) && (child as React.ReactElement<any>).type === Card) {
                let modifiedChild = child as React.ReactElement<any>;
                // Modifying here
                return modifiedChild;
            }
            // Returning other components / string.
            // Delete next line in case you dont need them.
            return child;
        });

Solution 6 - Validation

One has to use "React.isValidElement(child)" along with "child.type" if one is working with Typescript in order to avoid type mismatch errors.

React.Children.forEach(props.children, (child, index) => {
  if (React.isValidElement(child) && child.type !== Card) {
    error = new Error(
      '`' + componentName + '` only accepts children of type `Card`.'
    );
  }
});

Solution 7 - Validation

I made a custom PropType for this that I call equalTo. You can use it like this...

class MyChildComponent extends React.Component { ... }

class MyParentComponent extends React.Component {
  static propTypes = {
    children: PropTypes.arrayOf(PropTypes.equalTo(MyChildComponent))
  }
}

Now, MyParentComponent only accepts children that are MyChildComponent. You can check for html elements like this...

PropTypes.equalTo('h1')
PropTypes.equalTo('div')
PropTypes.equalTo('img')
...

Here is the implementation...

React.PropTypes.equalTo = function (component) {
  return function validate(propValue, key, componentName, location, propFullName) {
    const prop = propValue[key]
    if (prop.type !== component) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  };
}

You could easily extend this to accept one of many possible types. Maybe something like...

React.PropTypes.equalToOneOf = function (arrayOfAcceptedComponents) {
...
}

Solution 8 - Validation

static propTypes = {

  children : (props, propName, componentName) => {
              const prop = props[propName];
              return React.Children
                       .toArray(prop)
                       .find(child => child.type !== Card) && new Error(`${componentName} only accepts "<Card />" elements`);
  },

}

Solution 9 - Validation

You can add a prop to your Card component and then check for this prop in your CardGroup component. This is the safest way to achieve this in React.

This prop can be added as a defaultProp so it's always there.

class Card extends Component {

  static defaultProps = {
    isCard: true,
  }

  render() {
    return (
      <div>A Card</div>
    )
  }
}

class CardGroup extends Component {

  render() {
    for (child in this.props.children) {
      if (!this.props.children[child].props.isCard){
        console.error("Warning CardGroup has a child which isn't a Card component");
      }
    }

    return (
      <div>{this.props.children}</div>
    )
  }
}

Checking for whether the Card component is indeed a Card component by using type or displayName is not safe as it may not work during production use as indicated here: https://github.com/facebook/react/issues/6167#issuecomment-191243709

Solution 10 - Validation

I published the package that allows to validate the types of React elements https://www.npmjs.com/package/react-element-proptypes :

const ElementPropTypes = require('react-element-proptypes');
 
const Modal = ({ header, items }) => (
    <div>
        <div>{header}</div>
        <div>{items}</div>
    </div>
);
 
Modal.propTypes = {
    header: ElementPropTypes.elementOfType(Header).isRequired,
    items: React.PropTypes.arrayOf(ElementPropTypes.elementOfType(Item))
};
 
// render Modal 
React.render(
    <Modal
       header={<Header title="This is modal" />}
       items={[
           <Item/>,
           <Item/>,
           <Item/>
       ]}
    />,
    rootElement
);

Solution 11 - Validation

To validate correct children component i combine the use of react children foreach and the Custom validation proptypes, so at the end you can have the following:

HouseComponent.propTypes = {
children: PropTypes.oneOfType([(props, propName, componentName) => {
    let error = null;
    const validInputs = [
    'Mother',
    'Girlfried',
    'Friends',
    'Dogs'
    ];
    // Validate the valid inputs components allowed.
    React.Children.forEach(props[propName], (child) => {
            if (!validInputs.includes(child.type.name)) {
                error = new Error(componentName.concat(
                ' children should be one of the type:'
                    .concat(validInputs.toString())
            ));
        }
    });
    return error;
    }]).isRequired
};

As you can see is having and array with the name of the correct type.

On the other hand there is also a function called componentWithName from the airbnb/prop-types library that helps to have the same result. Here you can see more details

HouseComponent.propTypes = {
    children: PropTypes.oneOfType([
        componentWithName('SegmentedControl'),
        componentWithName('FormText'),
        componentWithName('FormTextarea'),
        componentWithName('FormSelect')
    ]).isRequired
};

Hope this help some one :)

Solution 12 - Validation

Considered multiple proposed approaches, but they all turned out to be either unreliable or overcomplicated to serve as a boilerplate. Settled on the following implementation.

class Card extends Component {
  // ...
}

class CardGroup extends Component {
  static propTypes = {
    children: PropTypes.arrayOf(
      (propValue, key, componentName) => (propValue[key].type !== Card)
        ? new Error(`${componentName} only accepts children of type ${Card.name}.`)
        : null
    )
  }
  // ...
}

Here're the key ideas:

  1. Utilize the built-in PropTypes.arrayOf() instead of looping over children
  2. Check the child type via propValue[key].type !== Card in a custom validator
  3. Use variable substitution ${Card.name} to not hard-code the type name

Library react-element-proptypes implements this in ElementPropTypes.elementOfType():

import ElementPropTypes from "react-element-proptypes";

class CardGroup extends Component {
  static propTypes = {
    children: PropTypes.arrayOf(ElementPropTypes.elementOfType(Card))
  }
  // ...
}

Solution 13 - Validation

An easy, production friendly check. At the top of your CardGroup component:

const cardType = (<Card />).type;

Then, when iterating over the children:

React.children.map(child => child.type === cardType ? child : null);

The nice thing about this check is that it will also work with library components/sub-components that don't expose the necessary classes to make an instanceof check work.

Solution 14 - Validation

Assert the type:

props.children.forEach(child =>
  console.assert(
    child.type.name == "CanvasItem",
    "CanvasScroll can only have CanvasItem component as children."
  )
)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbigblindView Question on Stackoverflow
Solution 1 - ValidationDiego VView Answer on Stackoverflow
Solution 2 - ValidationMarkView Answer on Stackoverflow
Solution 3 - ValidationmzabriskieView Answer on Stackoverflow
Solution 4 - ValidationSalimView Answer on Stackoverflow
Solution 5 - ValidationSLCH000View Answer on Stackoverflow
Solution 6 - ValidationKarnaView Answer on Stackoverflow
Solution 7 - ValidationCharlie MartinView Answer on Stackoverflow
Solution 8 - ValidationAbdennour TOUMIView Answer on Stackoverflow
Solution 9 - ValidationHedley SmithView Answer on Stackoverflow
Solution 10 - ValidationwizardzloyView Answer on Stackoverflow
Solution 11 - ValidationIsmael TerrenoView Answer on Stackoverflow
Solution 12 - ValidationSergii ShymkoView Answer on Stackoverflow
Solution 13 - ValidationJrdView Answer on Stackoverflow
Solution 14 - ValidationJoanView Answer on Stackoverflow