47

How can I validate that the supplied prop is a component class (not instance)?

e.g.

export default class TimelineWithPicker extends React.PureComponent {

    static propTypes = {
        component: PropTypes.any, // <-- how can I validate that this is a component class (or stateless functional component)?
    };

    render() {
        return (
            <this.props.component {...this.props} start={this.state.start}/>
        );
    }
}
Jose Da Silva Gomes
  • 3,814
  • 3
  • 24
  • 34
mpen
  • 272,448
  • 266
  • 850
  • 1,236

2 Answers2

67

For anyone using PropTypes >= 15.7.0 a new PropTypes.elementType was added in this pull request and was released on february 10, 2019.

This prop type supports all components (native components, stateless components, stateful components, forward refs React.forwardRef, context providers/consumers).

And it throws a warning when is not any of those elements, it also throws a warning when the prop passed is an element (PropTypes.element) and not a type.

Finally you can use it like any other prop type:

const propTypes = {
    component: PropTypes.elementType,
    requiredComponent: PropTypes.elementType.isRequired,
};
Jose Da Silva Gomes
  • 3,814
  • 3
  • 24
  • 34
  • 1
    If I understand what a [React element](https://en.reactjs.org/docs/rendering-elements.html) is, this prop type is actually quite badly named : > One might confuse elements with a more widely known concept of “components”. – challet Apr 14 '20 at 09:47
26

EDITED: Added React's FancyButton example to codesandbox as well as a custom prop checking function that works with the new React.forwardRef api in React 16.3. The React.forwardRef api returns an object with a render function. I'm using the following custom prop checker to verify this prop type. - Thanks for Ivan Samovar for noticing this need.

FancyButton: function (props, propName, componentName) {
  if(!props[propName] || typeof(props[propName].render) != 'function') {
    return new Error(`${propName}.render must be a function!`);
  }
}

You'll want to use PropTypes.element. Actually... PropType.func works for both stateless functional components and class components.

I've made a sandbox to prove that this works... Figured this was needed considering I gave you erroneous information at first. Very sorry about that!

Working sandbox example!

Here is the code for the test in case link goes dead:

import React from 'react';
import { render } from 'react-dom';
import PropTypes from "prop-types";

class ClassComponent extends React.Component {
  render() {
    return <p>I'm a class component</p>
  }
}

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

const FSComponent = () => (
    <p>I'm a functional stateless component</p>
);

const Test = ({ ClassComponent, FSComponent, FancyButton }) => (
  <div>
    <ClassComponent />
    <FSComponent />
    <FancyButton />
  </div>
);
Test.propTypes = {
  ClassComponent: PropTypes.func.isRequired,
  FSComponent: PropTypes.func.isRequired,
  FancyButton: function (props, propName, componentName) {
    if(!props[propName] || typeof(props[propName].render) != 'function') {
      return new Error(`${propName}.render must be a function!`);
    }
  },
}

render(<Test
         ClassComponent={ ClassComponent }
         FSComponent={ FSComponent }
         FancyButton={ FancyButton } />, document.getElementById('root'));
Kyle Richardson
  • 5,567
  • 3
  • 17
  • 40
  • 2
    Also use `oneOfType` for different types: `PropTypes.oneOfType([...])` – btzr Jul 26 '17 at 03:37
  • 1
    Isn't that an instance? I thought `` (aka `React.createElement(Foo, null)`) was a react element, whereas I want to check for raw `Foo`. – mpen Jul 26 '17 at 04:42
  • Nope :) There's a separate PropType for that. – Kyle Richardson Jul 26 '17 at 04:52
  • @btzr please check update if you have not resolved this issue. I made a sandbox so that you can see THIS does work. I ran into this in something I'm working on today, used element and thought, "dang... i need to correct that". Documentation could use updating about this. – Kyle Richardson Jul 27 '17 at 05:07
  • This approach doesn't work with components wrapped to `React.forwardRef` – new API introduced in react 16.3 – Ivan Samovar Apr 14 '18 at 11:46
  • @IvanSamovar Not true, it works perfectly. Here is the updated [working sandbox](https://codesandbox.io/s/znj0nyxll) with a copy paste of React's FancyButton example into it. – Kyle Richardson Apr 14 '18 at 15:44
  • @KyleRichardson I see error in your sandbox console `Failed prop type: Invalid prop \`FancyButton\` of type \`object\` supplied to \`Test\`, expected \`function\`.` screenshot http://take.ms/JwL0v – Ivan Samovar Apr 14 '18 at 16:57
  • @IvanSamovar You're right. Not sure how I missed that, I'll fix it. Thank you. – Kyle Richardson Apr 14 '18 at 17:06
  • 1
    so regarding to initial question there is no simple prop type that can validate any possible component type – Ivan Samovar Apr 14 '18 at 20:17
  • That was not the initial question. It was how to validate a class component. Though as of React v16 there seems to not be one catch all PropType. You could always use `PropTypes.oneOfType(PropTypes.func, customPropTypeFunc).isRequired;` – Kyle Richardson Apr 15 '18 at 04:17