Validating Props
Props are the contract between a component and whoever renders it. Validating that contract catches mistakes early, documents what a component expects, and powers editor autocomplete. In modern React the default approach is TypeScript, which checks your props at compile time and gives you instant feedback in the editor. For plain JavaScript projects the legacy prop-types package provides runtime checks instead. This page covers both, and when each makes sense.
Typing props with TypeScript
The recommended way to validate props in 2025 is to describe them with a TypeScript interface (or type) and annotate the component’s parameter. TypeScript then enforces the shape at compile time, so a missing or mistyped prop is a build error rather than a runtime surprise.
interface GreetingProps {
name: string;
age: number;
isAdmin: boolean;
}
function Greeting({ name, age, isAdmin }: GreetingProps) {
return (
<p>
{name} ({age}) {isAdmin ? "— administrator" : ""}
</p>
);
}
export default Greeting;
Rendering <Greeting name="Ada" age={36} isAdmin /> type-checks. Forgetting age, or passing age="36" as a string, produces an error in your editor and breaks tsc before the code ever runs.
Prefer
interfacefor public component props — it gives cleaner error messages and supports declaration merging. Usetypewhen you need unions, intersections, or mapped types.
Required vs optional props
By default every property in an interface is required. Mark a prop optional by appending ? to its name. Optional props arrive as undefined when the parent omits them, so handle that case in your logic.
interface ButtonProps {
label: string; // required
variant?: "solid" | "ghost"; // optional, union-typed
disabled?: boolean; // optional
}
function Button({ label, variant = "solid", disabled = false }: ButtonProps) {
return (
<button className={`btn btn-${variant}`} disabled={disabled}>
{label}
</button>
);
}
Default values
Defaults belong in the destructuring pattern, not in a separate defaultProps object (which is deprecated for function components in React 19). Notice how variant and disabled above fall back to sensible values while remaining optional in the type.
Typing children and events
React ships ready-made types for common cases. Use React.ReactNode for renderable children and the synthetic event types for handlers.
interface CardProps {
title: string;
children: React.ReactNode;
onSelect?: (id: string) => void;
}
function Card({ title, children, onSelect }: CardProps) {
return (
<section onClick={() => onSelect?.(title)}>
<h2>{title}</h2>
{children}
</section>
);
}
Legacy runtime validation with prop-types
Before TypeScript became standard, the prop-types package validated props at runtime. It still works and is useful in plain JavaScript codebases where a compile step isn’t available. Install it first:
npm install prop-types
Then attach a propTypes object to the component. React checks the values during development and logs a console warning on a mismatch.
import PropTypes from "prop-types";
function Greeting({ name, age, isAdmin }) {
return (
<p>
{name} ({age}) {isAdmin ? "— administrator" : ""}
</p>
);
}
Greeting.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
isAdmin: PropTypes.bool,
};
export default Greeting;
If you render <Greeting name="Ada" age="36" />, the validator fires a warning:
Output:
Warning: Failed prop type: Invalid prop `age` of type `string`
supplied to `Greeting`, expected `number`.
The chained .isRequired marks a prop as mandatory; omitting it makes the prop optional. There are validators for every primitive plus arrayOf, shape, oneOf, oneOfType, and func.
TypeScript vs prop-types
| Aspect | TypeScript | prop-types |
|---|---|---|
| When it checks | Compile time | Runtime (dev only) |
| Editor autocomplete | Yes | No |
| Build dependency | Needs tsc/Vite TS | Plain JS, no build step |
| Stripped in production | N/A (types erased) | Should be stripped manually |
| Status | Recommended default | Legacy / maintenance |
| Catches | Most type errors before run | Errors only when code executes |
The two are not usually combined: if you already use TypeScript, prop-types is redundant noise. Reach for prop-types only in JavaScript projects, or temporarily while migrating a codebase to TypeScript.
prop-typeswarnings are silenced in production builds and add bundle weight. TypeScript types vanish entirely at build time, costing zero runtime bytes — another reason it’s the modern choice.
Best Practices
- Use TypeScript interfaces as the default way to validate props in new projects.
- Make props required by default; mark something optional only when the component genuinely works without it.
- Provide defaults in the destructuring pattern instead of the deprecated
defaultProps. - Use precise types — string-literal unions like
"solid" | "ghost"beat a barestring. - Type children with
React.ReactNodeand handlers with React’s synthetic event types. - Reserve
prop-typesfor plain JavaScript codebases, and remember its checks run only in development. - Don’t pair
prop-typeswith TypeScript — pick one validation strategy per component.