Validating Props with PropTypes
When building components that will be used by other developers (or by your future self), it is helpful to declare exactly which props a component expects and what type each one should be. The prop-types package provides runtime type-checking for React props in development. When a wrong type is passed, PropTypes prints a descriptive warning to the browser console.
Installing prop-types
npm install prop-types
Defining PropTypes on a Component
After defining your component, attach a propTypes object to it. Each key is a prop name; the value is a validator from the PropTypes object:
import PropTypes from 'prop-types'
function UserCard({ name, age, email, isPremium, onFollow }) {
return (
<div className="card">
<h2>{name}</h2>
<p>{age} years old</p>
<p>{email}</p>
{isPremium && <span className="badge">Premium</span>}
<button onClick={onFollow}>Follow</button>
</div>
)
}
UserCard.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string.isRequired,
isPremium: PropTypes.bool,
onFollow: PropTypes.func.isRequired,
}
export default UserCardBuilt-in Validators
PropTypes.string— the prop must be a stringPropTypes.number— the prop must be a numberPropTypes.bool— the prop must be a booleanPropTypes.func— the prop must be a functionPropTypes.array— the prop must be an array (any array)PropTypes.object— the prop must be an objectPropTypes.node— anything renderable: string, number, element, array, fragmentPropTypes.element— a single React element (<Component />)PropTypes.symbol— a JavaScript SymbolPropTypes.any— any value (use sparingly)
isRequired
Append .isRequired to any validator to make the prop mandatory. If the prop is missing or undefined, PropTypes logs a warning:
UserCard.propTypes = {
name: PropTypes.string.isRequired, // warning if missing
email: PropTypes.string.isRequired, // warning if missing
age: PropTypes.number, // optional — no warning if undefined
}PropTypes.shape — Validating Object Structure
When a prop is an object with a specific shape, use PropTypes.shape() to validate each key independently:
import PropTypes from 'prop-types'
function ProductCard({ product, onAddToCart }) {
return (
<div className="product-card">
<img src={product.imageUrl} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
<p>{product.inStock ? 'In Stock' : 'Out of Stock'}</p>
<button onClick={() => onAddToCart(product.id)} disabled={!product.inStock}>
Add to Cart
</button>
</div>
)
}
ProductCard.propTypes = {
product: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
imageUrl: PropTypes.string.isRequired,
inStock: PropTypes.bool.isRequired,
}).isRequired,
onAddToCart: PropTypes.func.isRequired,
}PropTypes.arrayOf — Typed Arrays
// An array of strings:
tags: PropTypes.arrayOf(PropTypes.string)
// An array of shaped objects:
ProductList.propTypes = {
products: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
})
).isRequired,
}PropTypes.oneOf — Enum Values
// Restrict to a specific set of allowed values:
Button.propTypes = {
variant: PropTypes.oneOf(['primary', 'secondary', 'danger']),
size: PropTypes.oneOf(['sm', 'md', 'lg']),
}
// oneOfType — accept multiple types:
Badge.propTypes = {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
}Custom Validators
For complex rules, write a custom validator — a function that returns an Error on failure and null on success:
Avatar.propTypes = {
// Must be a positive number between 16 and 512
size: (props, propName, componentName) => {
const val = props[propName]
if (typeof val !== 'number') {
return new Error(
`${componentName}: '${propName}' must be a number, got ${typeof val}`
)
}
if (val < 16 || val > 512) {
return new Error(
`${componentName}: '${propName}' must be between 16 and 512, got ${val}`
)
}
return null
},
}Pairing with defaultProps
You can pair PropTypes with defaultProps to document both the expected type and the fallback value in one place (though ES6 default parameters are preferred in modern code):
function Button({ label, variant, size }) {
return <button className={`btn btn--${variant} btn--${size}`}>{label}</button>
}
Button.propTypes = {
label: PropTypes.string.isRequired,
variant: PropTypes.oneOf(['primary', 'secondary', 'danger']),
size: PropTypes.oneOf(['sm', 'md', 'lg']),
}
// Legacy defaultProps — prefer ES6 defaults in the function signature
Button.defaultProps = {
variant: 'primary',
size: 'md',
}PropTypes vs TypeScript
PropTypes check types at runtime (in the browser console during development). TypeScript checks types at compile time (in your editor and CI). For new projects TypeScript is the strongly recommended approach — it catches errors before the code even runs, enables autocomplete, and requires no extra runtime dependency.
If your project already uses TypeScript, you generally do not need PropTypes — the TypeScript compiler provides the same guarantees (and more) without the runtime overhead:
// TypeScript alternative — no prop-types package needed
interface UserCardProps {
name: string
age: number
email: string
isPremium?: boolean
onFollow: () => void
}
function UserCard({ name, age, email, isPremium = false, onFollow }: UserCardProps) {
return (
<div>
<h2>{name}</h2>
<p>{age} · {email}</p>
{isPremium && <span>Premium</span>}
<button onClick={onFollow}>Follow</button>
</div>
)
}