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.
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.
In addition to validating arrays and primitive value prop types, we can also use prop-types
to validate more complex 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.
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.
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
.
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.
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
.
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.
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.
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.