Telerik blogs

Since parent components can pass down any data as props to child components, we need to validate the data type to ensure the child gets what it expects. Let’s take a look!

React is a JavaScript library used for creating interactive web frontend applications. It is one of the most popular libraries because of its easy-to-use API.

We combine components into an app by passing data from parent components to child components. To do this, we pass data with props. Props are like HTML attributes, but they can contain dynamic data.

A parent component passes props down to child components. And child components receive them. We can pass any data as props. Therefore, we need a way to validate their data type so that the child component gets what they expect.

In this article, we will look at how to validate prop data types with the prop-types library.

Installing the prop-types Library and Basic Usage

To install the prop-types library, we run:

npm i prop-types

Then we can use it by writing:

import PropTypes from "prop-types";

const FooComponent = ({
  optionalArray,
  optionalBool,
  optionalFunc,
  optionalNumber,
  optionalObject,
  optionalString,
  optionalSymbol,
}) => {
  return (
    <>
      <div>{JSON.stringify(optionalArray)}</div>
      <div>{optionalBool}</div>
      <div>{JSON.stringify(optionalFunc)}</div>
      <div>{optionalNumber}</div>
      <div>{JSON.stringify(optionalObject)}</div>
      <div>{optionalString}</div>
      <div>{optionalSymbol.toString()}</div>
    </>
  );
};

FooComponent.propTypes = {
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,
};

export default FooComponent;

We define the FooComponent which takes several props.

And we validate them by setting the propTypes property of the FooComponent component to an object that has the names of the props to validate as the keys. And the corresponding values are the data type of the props.

Since we didn’t specify that the props are required explicitly, they are optional.

Then to use it, we write:

import FooComponent from "./Foo";
import "./styles.css";

export default function App() {
  return (
    <FooComponent
      optionalArray={[1, 2, 3]}
      optionalBool={false}
      optionalFunc={() => "foo"}
      optionalNumber={123}
      optionalObject={{ foo: "bar" }}
      optionalString="abc"
      optionalSymbol={Symbol("abc")}
    />
  );
}

We pass in the values for the props when we reference the component in App.

As a result, we see:

[1,2,3]
123
{"foo":"bar"}
abc
Symbol(abc)

rendered on the screen.

If we pass in prop values that don’t match the type specified, we get an error.

For instance, if we write:

import FooComponent from "./Foo";

export default function App() {
  return (
    <FooComponent
      optionalArray="fooobar"
      optionalBool={false}
      optionalFunc={() => "foo"}
      optionalNumber={123}
      optionalObject={{ foo: "bar" }}
      optionalString="abc"
      optionalSymbol={Symbol("abc")}
    />
  );
}

Then we get the “Warning: Failed prop type: Invalid prop optionalArray of type string supplied to FooComponent, expected array.” error in the console since the optionalArray prop is supposed to be an array according to the FooComponent.propTypes.optionalArray value.

More Complex Prop Types

In addition to validating arrays and primitive value prop types, we can also use prop-types to validate more complex types.

Validate React Entity Types

We can use prop-types to validate data types of various React entities.

For instance, we write:

Foo.js

import PropTypes from "prop-types";

const FooComponent = ({
  optionalNode,
  optionalElement,
  optionalElementType: OptionalElementType,
}) => {
  return (
    <>
      <div>{optionalNode}</div>
      <div>{optionalElement}</div>
      <div>{<OptionalElementType />}</div>
    </>
  );
};

FooComponent.propTypes = {
  optionalNode: PropTypes.node,
  optionalElement: PropTypes.element,
  optionalElementType: PropTypes.elementType,
};

export default FooComponent;

Bar.js

const BarComponent = () => <p>bar</p>;

export default BarComponent;

App.js

import BarComponent from "./Bar";
import FooComponent from "./Foo";

export default function App() {
  return (
    <FooComponent
      optionalNode={<>bar</>}
      optionalElement={<p>foo</p>}
      optionalElementType={BarComponent}
    />
  );
}

In Foo.js, we changed it to take the optionalNode, optionalElement, and optionalElementType props.

optionalNode is set to the node type, which is a React node. A node is any entity that is not an element or component.

The optionalElement prop is set to the element type. The element type is a React element like div, p, etc.

The optionalElementType is set to the elementType type, which are components.

Therefore, from App, we see that we set optionalNode to a fragment, optionalElement to a p element, and optionalElementType to BarComponent to avoid validation errors.

Validate Objects

The prop-types library also lets us validate that an object is an instance of a given constructor or class.

For instance, we write:

Person.js

export class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

Foo.js

import PropTypes from "prop-types";
import { Person } from "./Person";

const FooComponent = ({ optionalPerson }) => {
  const { firstName, lastName } = optionalPerson ?? {};
  return (
    <>
      <div>
        {firstName} {lastName}
      </div>
    </>
  );
};

FooComponent.propTypes = {
  optionalPerson: PropTypes.instanceOf(Person),
};

export default FooComponent;

App.js

import FooComponent from "./Foo";
import { Person } from "./Person";

export default function App() {
  return <FooComponent optionalPerson={new Person("jane", "smith")} />;
}

We create the Person class and set that as the type of the optionalPerson prop.

We validate that a prop has the type being an instance of a class or constructor with the instanceOf method. Then we pass in a Person instance as the valid of FooComponent's optionalPerson prop.

Therefore, we see:

jane smith

rendered on the screen.

In addition to validating an instance of a prop, we can also validate the content of an object or array. To do this, we can use the objectOf, arrayOf or shape methods.

For instance, we write:

Foo.js

import PropTypes from "prop-types";

const FooComponent = ({
  optionalNumArray,
  optionalNumObject,
  optionalPersonObject,
}) => {
  const { firstName, lastName } = optionalPersonObject ?? {};
  return (
    <>
      <div>{JSON.stringify(optionalNumArray)}</div>
      <div>{JSON.stringify(optionalNumObject)}</div>
      <div>
        {firstName} {lastName}
      </div>
    </>
  );
};

FooComponent.propTypes = {
  optionalNumArray: PropTypes.arrayOf(PropTypes.number),
  optionalNumObject: PropTypes.objectOf(PropTypes.number),
  optionalPersonObject: PropTypes.shape({
    firstName: PropTypes.string,
    lastName: PropTypes.string,
  }),
};

export default FooComponent;

App.js

import FooComponent from "./Foo";

export default function App() {
  return (
    <FooComponent
      optionalNumArray={[1, 2, 3]}
      optionalNumObject={{ foo: 1, bar: 2 }}
      optionalPersonObject={{ firstName: "jane", lastName: "smith" }}
    />
  );
}

to add the optionalNumArray, optionalNumObject and optionalPersonObject props.

We use the arrayOf method to validate that optionalNumArray takes an array of numbers since we called arrayOf with PropTypes.number and set that as the value of optionalNumArray.

optionalNumObject accepts an object with all property values set to numbers since we set it to the prop type returned by calling objectOf with PropTypes.number.

Likewise, optionalPersonObject takes an object with the firstName and lastName properties all set to strings since we called shape with an object with the properties set to PropTypes.string.

Any object with the firstName and lastName properties set to string are allowed even if they have other properties included.

To check for the exact shape of an object, we can use exact instead of shape.

For instance, we write:

Foo.js

import PropTypes from "prop-types";

const FooComponent = ({ optionalPersonObject }) => {
  const { firstName, lastName } = optionalPersonObject ?? {};
  return (
    <>
      <div>
        {firstName} {lastName}
      </div>
    </>
  );
};

FooComponent.propTypes = {
  optionalPersonObject: PropTypes.exact({
    firstName: PropTypes.string,
    lastName: PropTypes.string,
  }),
};

export default FooComponent;

and

App.js

import FooComponent from "./Foo";

export default function App() {
  return (
    <FooComponent
      optionalPersonObject={{ firstName: "jane", lastName: "smith" }}
    />
  );
}

to pass in a valid value for the optionalPersonObject prop.

If we have extra properties, have values with invalid types, or if the casing of the property names, then we will see an error in the console.

Validate Enum Value

We can use the oneOf method to specify that a prop can take one of the values listed.

For instance, we write:

Foo.js

import PropTypes from "prop-types";

const FooComponent = ({ optionalEnum }) => {
  return (
    <>
      <div>{optionalEnum}</div>
    </>
  );
};

FooComponent.propTypes = {
  optionalEnum: PropTypes.oneOf(["foo", "bar"]),
};

export default FooComponent;

App.js

import FooComponent from "./Foo";

export default function App() {
  return <FooComponent optionalEnum="foo" />;
}

We specify that the optionalEnum prop can either be 'foo' or 'bar' with oneOf.

Validate Props That Can Be One of Multiple Types

We can validate props that can be one of multiple types. To do this, we call oneOfType with an array of types.

For example, we write:

Foo.js

import PropTypes from "prop-types";
import { Person } from "./Person";

const FooComponent = ({ optionalUnion }) => {
  return (
    <>
      <div>{JSON.stringify(optionalUnion)}</div>
    </>
  );
};

FooComponent.propTypes = {
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Person),
  ]),
};

export default FooComponent;

Person.js

export class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

App.js

import FooComponent from "./Foo";

export default function App() {
  return <FooComponent optionalUnion="foo" />;
}

The optionalUnion prop can be one of string, number or a Person class or constructor instance as specified by the content of the array we call oneOfType with.

Required Props

We can make a prop required with the isRequired property.

For example, we write:

Foo.js

import PropTypes from "prop-types";

const FooComponent = ({ requiredNum }) => {
  return (
    <>
      <div>{requiredNum}</div>
    </>
  );
};

FooComponent.propTypes = {
  requiredNum: PropTypes.number.isRequired,
};

export default FooComponent;

App.js

import FooComponent from "./Foo";

export default function App() {
  return <FooComponent requiredNum={133} />;
}

to make the requiredNum prop a required prop that accepts a number by setting it to PropTypes.number.isRequired.

Custom Validators

We can also add a validator function that lets us validate props however we want.

For instance, we can check that the props passed into FooComponent all have value 'foo' with:

Foo.js

const FooComponent = ({ customProp }) => {
  return (
    <>
      <div>{customProp}</div>
    </>
  );
};

FooComponent.propTypes = {
  customProp: (props, propName, componentName) => {
    if (!/foo/.test(props[propName])) {
      return new Error(
        `Invalid prop ${props[propName]} supplied to ${propName} component ${componentName}`
      );
    }
  },
};

export default FooComponent;

App.js

import FooComponent from "./Foo";

export default function App() {
  return <FooComponent customProp="foo" />;
}

We set the FooComponent.propTypes.customProp property to a function that checks if props[propName] is 'foo'. If it’s not, we throw an error.

props is an object with all the props passed in. The property keys are the prop names and the values are the prop values.

propName has the name of the prop as a string. componentName is the component name string. Since we set customProp to 'foo', no error is raised.

However, if we change App.js to:

import FooComponent from "./Foo";

export default function App() {
  return <FooComponent customProp="bar" />;
}

Then we see “Warning: Failed prop type: Invalid prop bar supplied to customProp component FooComponent” displayed in the console.

Conclusion

We piece together components to build a frontend app with React.

A parent component passes props down to child components. And child components receive them. This lets components communicate with each other so that we can use data in the places we want.

We can pass any data as props. Therefore, we need a way to validate their data type so that the child component gets what they expect.

The prop-types library makes component prop data type validation easy by providing us with ways to validate any prop types.


About the Author

John Au-Yeung

John Au-Yeung is a frontend developer with 6+ years of experience. He is an avid blogger (visit his site at https://thewebdev.info/) and the author of Vue.js 3 By Example.

Related Posts

Comments

Comments are disabled in preview mode.