ReactJs: What should the PropTypes be for this.props.children?
ReactjsJsxReact ProptypesReactjs Problem Overview
Given a simple component that renders its children:
class ContainerComponent extends Component {
static propTypes = {
children: PropTypes.object.isRequired,
}
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
export default ContainerComponent;
Question: What should the propType of the children prop be?
When I set it as an object, it fails when I use the component with multiple children:
<ContainerComponent>
<div>1</div>
<div>2</div>
</ContainerComponent>
> Warning: Failed prop type: Invalid prop children
of type array
> supplied to ContainerComponent
, expected object
.
If I set it as an array, it will fail if I give it only one child, i.e.:
<ContainerComponent>
<div>1</div>
</ContainerComponent>
> Warning: Failed prop type: Invalid prop children of type object > supplied to ContainerComponent, expected array.
Please advise, should I just not bother doing a propTypes check for children elements?
Reactjs Solutions
Solution 1 - Reactjs
Try something like this utilizing oneOfType
or PropTypes.node
import PropTypes from 'prop-types'
...
static propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]).isRequired
}
or
static propTypes = {
children: PropTypes.node.isRequired,
}
Solution 2 - Reactjs
For me it depends on the component. If you know what you need it to be populated with then you should try to specify exclusively, or multiple types using:
PropTypes.oneOfType
If you want to refer to a React component then you will be looking for
PropTypes.element
Although,
PropTypes.node
describes anything that can be rendered - strings, numbers, elements or an array of these things. If this suits you then this is the way.
With very generic components, who can have many types of children, you can also use the below. However I wouldn't recommend it. As mentioned in the comments below, it does somewhat defeat the point of using PropTypes
and there are usually other ways to specify what your component requires. Also bare in mind that eslint and ts may (probably) not be happy with this lack of specificity:
PropTypes.any
Solution 3 - Reactjs
The PropTypes documentation has the following
// Anything that can be rendered: numbers, strings, elements or an array
// (or fragment) containing these types.
optionalNode: PropTypes.node,
So, you can use PropTypes.node
to check for objects or arrays of objects
static propTypes = {
children: PropTypes.node.isRequired,
}
Solution 4 - Reactjs
The answers here don't seem to quite cover checking the children exactly. node
and object
are too permissive, I wanted to check the exact element. Here is what I ended up using:
- Use
oneOfType([])
to allow for single or array of children - Use
shape
andarrayOf(shape({}))
for single and array of children, respectively - Use
oneOf
for the child element itself
In the end, something like this:
import PropTypes from 'prop-types'
import MyComponent from './MyComponent'
children: PropTypes.oneOfType([
PropTypes.shape({
type: PropTypes.oneOf([MyComponent]),
}),
PropTypes.arrayOf(
PropTypes.shape({
type: PropTypes.oneOf([MyComponent]),
})
),
]).isRequired
This issue helped me figure this out more clearly: https://github.com/facebook/react/issues/2979
Solution 5 - Reactjs
If you want to match exactly a component type, check this
MenuPrimary.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(MenuPrimaryItem),
PropTypes.objectOf(MenuPrimaryItem)
])
}
If you want to match exactly some component types, check this
const HeaderTypes = [ PropTypes.objectOf(MenuPrimary), PropTypes.objectOf(UserInfo)]
Header.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([...HeaderTypes])),
...HeaderTypes
])
}
Solution 6 - Reactjs
If you want to include render prop components:
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
PropTypes.func
])
Solution 7 - Reactjs
Try a custom propTypes :
const childrenPropTypeLogic = (props, propName, componentName) => {
const prop = props[propName];
return React.Children
.toArray(prop)
.find(child => child.type !== 'div') && new Error(`${componentName} only accepts "div" elements`);
};
static propTypes = {
children : childrenPropTypeLogic
}
const {Component, PropTypes} = React;
const childrenPropTypeLogic = (props, propName, componentName) => {
var error;
var prop = props[propName];
React.Children.forEach(prop, function (child) {
if (child.type !== 'div') {
error = new Error(
'`' + componentName + '` only accepts children of type `div`.'
);
}
});
return error;
};
class ContainerComponent extends Component {
static propTypes = {
children: childrenPropTypeLogic,
}
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
class App extends Component {
render(){
return (
<ContainerComponent>
<div>1</div>
<div>2</div>
</ContainerComponent>
)
}
}
ReactDOM.render(<App /> , document.querySelector('section'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<section />
Solution 8 - Reactjs
Example:
import React from 'react';
import PropTypes from 'prop-types';
class MenuItem extends React.Component {
render() {
return (
<li>
<a href={this.props.href}>{this.props.children}</a>
</li>
);
}
}
MenuItem.defaultProps = {
href: "/",
children: "Main page"
};
MenuItem.propTypes = {
href: PropTypes.string.isRequired,
children: PropTypes.string.isRequired
};
export default MenuItem;
Picture: Shows you error in console if the expected type is different
Solution 9 - Reactjs
Accoriding to propTypes documentation element
is used for React element, and children is one of them:
// A React element.
optionalElement: PropTypes.element,
Then you can combine it with arrayOf
Component.propTypes = { children: PropTypes.arrayOf(PropTypes.element).isRequired, };
Then it will require at least one children
.