Getting started
Introduction
PropTypes provide runtime type checking for React component props. They warn developers when incorrect prop types are passed, helping catch bugs during development. PropTypes are stripped in production builds for performance.
Installation
npm install prop-types
Basic usage
import PropTypes from "prop-types";
function UserCard({ name, age, email }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
}
UserCard.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
email: PropTypes.string,
};
PropTypes are defined as a static property on the component. Use .isRequired to make props mandatory.
Primitive types
Basic validators
| Validator | Description |
|---|---|
PropTypes.string |
String value |
PropTypes.number |
Number value |
PropTypes.bool |
Boolean value |
PropTypes.func |
Function |
PropTypes.symbol |
Symbol |
PropTypes.bigint |
BigInt value |
Example
MyComponent.propTypes = {
title: PropTypes.string,
count: PropTypes.number,
isActive: PropTypes.bool,
onClick: PropTypes.func,
id: PropTypes.symbol,
largeValue: PropTypes.bigint,
};
Required props
MyComponent.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
callback: PropTypes.func.isRequired,
};
Chain .isRequired after any validator to make the prop mandatory.
Complex types
React types
| Validator | Description |
|---|---|
PropTypes.node |
Anything renderable (string, number, element, array) |
PropTypes.element |
React element |
PropTypes.elementType |
React component type |
PropTypes.instanceOf(Class) |
Instance of a class |
Collections
| Validator | Description |
|---|---|
PropTypes.array |
Any array |
PropTypes.object |
Any object |
PropTypes.arrayOf(type) |
Array of specific type |
PropTypes.objectOf(type) |
Object with values of specific type |
Example
import Message from "./Message";
MyComponent.propTypes = {
// Renderable content
children: PropTypes.node,
// React element
icon: PropTypes.element,
// Component type
component: PropTypes.elementOf,
// Class instance
message: PropTypes.instanceOf(Message),
// Collections
tags: PropTypes.arrayOf(PropTypes.string),
scores: PropTypes.objectOf(PropTypes.number),
};
Enumerables
oneOf - Enum values
MyComponent.propTypes = {
direction: PropTypes.oneOf(["left", "right", "up", "down"]),
size: PropTypes.oneOf(["sm", "md", "lg"]),
status: PropTypes.oneOf(["pending", "success", "error"]),
};
Use oneOf to restrict prop values to a specific set of options.
oneOfType - Union types
MyComponent.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
content: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
PropTypes.arrayOf(PropTypes.element),
]),
value: PropTypes.oneOfType([PropTypes.number, PropTypes.instanceOf(Date)]),
};
Use oneOfType when a prop can accept multiple different types.
Shape validation
shape - Object shape
MyComponent.propTypes = {
user: PropTypes.shape({
name: PropTypes.string,
age: PropTypes.number,
email: PropTypes.string,
}),
};
Allows extra properties not defined in the shape.
// ✅ Valid - extra "id" property is allowed
<MyComponent
user={{
name: "John",
age: 30,
id: 123,
}}
/>
exact - Exact object shape
MyComponent.propTypes = {
user: PropTypes.exact({
name: PropTypes.string,
age: PropTypes.number,
}),
};
Warns if extra properties are present.
// ⚠️ Warning - extra "id" property not allowed
<MyComponent
user={{
name: "John",
age: 30,
id: 123,
}}
/>
Nested shapes
MyComponent.propTypes = {
address: PropTypes.shape({
street: PropTypes.string,
city: PropTypes.string,
coordinates: PropTypes.shape({
lat: PropTypes.number.isRequired,
lng: PropTypes.number.isRequired,
}),
}),
};
Shapes can be nested to validate complex object structures.
Array of shapes
MyComponent.propTypes = {
users: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
email: PropTypes.string,
}),
).isRequired,
};
Combine arrayOf with shape to validate arrays of objects.
Custom validators
Basic custom validator
MyComponent.propTypes = {
email: function (props, propName, componentName) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(props[propName])) {
return new Error(
`Invalid prop \`${propName}\` supplied to \`${componentName}\`. ` +
`Expected a valid email address.`,
);
}
},
};
Custom validators receive props, propName, and componentName. Return an Error object on failure, null on success.
Custom array validator
MyComponent.propTypes = {
scores: PropTypes.arrayOf(
function (propValue, key, componentName, location, propFullName) {
if (propValue[key] < 0 || propValue[key] > 100) {
return new Error(
`Invalid prop \`${propFullName}\` supplied to \`${componentName}\`. ` +
`Score must be between 0 and 100.`,
);
}
},
),
};
Array validators receive the array value, key, and additional location info.
Validator rules
- Return
new Error(message)on validation failure - Return
null(or nothing) on success - Don't use
console.warn()orconsole.error() - Don't throw errors directly
- Avoid side effects
- Keep validators simple (they run on every render in dev mode)
Reusable validators
const emailValidator = (props, propName, componentName) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(props[propName])) {
return new Error(`Invalid email in ${componentName}.${propName}`);
}
};
Component1.propTypes = { email: emailValidator };
Component2.propTypes = { userEmail: emailValidator };
Extract validators into reusable functions for consistency.
Default props
Function component
function Hello({ name, greeting }) {
return (
<div>
{greeting}, {name}!
</div>
);
}
Hello.propTypes = {
name: PropTypes.string,
greeting: PropTypes.string,
};
Hello.defaultProps = {
name: "Stranger",
greeting: "Hello",
};
Define defaultProps as a static property on function components.
Class component
class Hello extends React.Component {
static propTypes = {
name: PropTypes.string,
greeting: PropTypes.string,
};
static defaultProps = {
name: "Stranger",
greeting: "Hello",
};
render() {
return (
<div>
{this.props.greeting}, {this.props.name}!
</div>
);
}
}
Use static class properties for propTypes and defaultProps.
Default prop behavior
// Uses default
<Hello />
// → name = 'Stranger'
// Uses default (undefined triggers default)
<Hello name={undefined} />
// → name = 'Stranger'
// Does NOT use default (null is a value)
<Hello name={null} />
// → name = null
Only undefined triggers default values. null is treated as an explicit value.
With object destructuring
function Button({ variant = "primary", size = "md", children }) {
return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
}
// Can still use PropTypes
Button.propTypes = {
variant: PropTypes.oneOf(["primary", "secondary"]),
size: PropTypes.oneOf(["sm", "md", "lg"]),
children: PropTypes.node,
};
You can use ES6 default parameters instead of defaultProps, but they work slightly differently with destructuring.
Component patterns
Children validation
// Single React element required
MyComponent.propTypes = {
children: PropTypes.element.isRequired,
};
// Any renderable content
MyComponent.propTypes = {
children: PropTypes.node,
};
// Array of elements
MyComponent.propTypes = {
children: PropTypes.arrayOf(PropTypes.element),
};
// Specific element type
MyComponent.propTypes = {
children: PropTypes.instanceOf(MyChildComponent),
};
With React.forwardRef
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className={props.className}>
{props.children}
</button>
));
FancyButton.displayName = "FancyButton";
FancyButton.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
};
PropTypes work normally with forwardRef components.
With React.memo
const MemoComponent = React.memo(function MyComponent({ name, age }) {
return (
<div>
{name} is {age} years old
</div>
);
});
MemoComponent.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
};
PropTypes are defined on the memoized component wrapper.
Higher-order component
function withAuth(Component) {
const WithAuth = (props) => {
// HOC logic...
return <Component {...props} />;
};
WithAuth.propTypes = {
...Component.propTypes,
isAuthenticated: PropTypes.bool,
};
return WithAuth;
}
HOCs can inherit and extend PropTypes from wrapped components.
TypeScript migration
Type equivalents
| PropTypes | TypeScript |
|---|---|
PropTypes.string |
string |
PropTypes.number |
number |
PropTypes.bool |
boolean |
PropTypes.func |
() => void |
PropTypes.symbol |
symbol |
PropTypes.array |
any[] |
PropTypes.object |
object |
React types
| PropTypes | TypeScript |
|---|---|
PropTypes.node |
React.ReactNode |
PropTypes.element |
React.ReactElement |
PropTypes.elementType |
React.ElementType |
PropTypes.instanceOf(Message) |
Message |
Advanced types
| PropTypes | TypeScript |
|---|---|
PropTypes.oneOf(['a', 'b']) |
'a' | 'b' |
PropTypes.oneOfType([...]) |
Type1 | Type2 |
PropTypes.arrayOf(PropTypes.number) |
number[] |
PropTypes.objectOf(PropTypes.number) |
Record<string, number> |
PropTypes.shape({...}) |
interface { ... } |
.isRequired |
Remove ? (non-optional) |
Before (PropTypes)
import PropTypes from "prop-types";
function UserCard({ name, age, tags, onEdit }) {
return <div>{/* ... */}</div>;
}
UserCard.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
tags: PropTypes.arrayOf(PropTypes.string).isRequired,
onEdit: PropTypes.func,
};
After (TypeScript)
interface UserCardProps {
name: string;
age?: number;
tags: string[];
onEdit?: () => void;
}
function UserCard({ name, age, tags, onEdit }: UserCardProps) {
return <div>{/* ... */}</div>;
}
Shape to interface
// PropTypes
PropTypes.shape({
user: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
email: PropTypes.string,
}).isRequired,
permissions: PropTypes.arrayOf(PropTypes.string),
});
// TypeScript
interface User {
id: number;
name: string;
email?: string;
}
interface Props {
user: User;
permissions?: string[];
}
Gotchas & tips
Common mistakes
// ❌ Wrong - isRequired before validator
PropTypes.isRequired.string;
// ✅ Correct - isRequired after validator
PropTypes.string.isRequired;
// ❌ Wrong - old React.PropTypes (deprecated)
React.PropTypes.string;
// ✅ Correct - use prop-types package
PropTypes.string;
// ❌ Too permissive
PropTypes.object;
// ✅ Better - use shape
PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string,
});
null vs undefined
Component.defaultProps = {
value: 'default'
}
// undefined → uses default
<Component value={undefined} />
// value = 'default'
// null → does NOT use default
<Component value={null} />
// value = null
Remember: null is an explicit value and won't trigger defaultProps.
Performance notes
- PropTypes only run in development mode
- Stripped from production builds (with proper tooling)
- Run on every render in dev mode
- Keep custom validators simple
- Consider migrating to TypeScript for large codebases
PropTypes.any
// ⚠️ Avoid - defeats the purpose
MyComponent.propTypes = {
data: PropTypes.any,
};
// ✅ Better - be specific
MyComponent.propTypes = {
data: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.object,
]),
};
Avoid PropTypes.any - it provides no validation benefit.
Production stripping
Babel plugin
npm install --save-dev babel-plugin-transform-react-remove-prop-types
{
"env": {
"production": {
"plugins": [
[
"transform-react-remove-prop-types",
{
"removeImport": true
}
]
]
}
}
}
This plugin removes PropTypes code from production builds to reduce bundle size.
Webpack (DefinePlugin)
// webpack.config.js
module.exports = {
plugins: [
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("production"),
}),
],
};
PropTypes checks are automatically disabled when NODE_ENV === 'production'.
Manual checking
import PropTypes from "prop-types";
// Manual validation (rarely needed)
PropTypes.checkPropTypes(MyComponent.propTypes, props, "prop", "MyComponent");
Manually invoke PropTypes validation. This is rarely needed - React calls this automatically.
Bundle size impact
| Bundle | Size |
|---|---|
| With PropTypes | ~30KB |
| Without PropTypes | ~0KB |
PropTypes add ~30KB to bundles. Always strip in production.
Also see
- PropTypes official documentation - React legacy docs
- prop-types package - npm package
- prop-types GitHub - Source code and issues
- TypeScript React - Migrating to TypeScript
- React documentation - Modern React docs