PropTypes → TypeScript
Philosophy shift
PropTypes validate props at runtime in development — errors show up in the console after the component renders. TypeScript catches mismatches at compile time, before the code runs. Once on TypeScript, PropTypes are redundant.
Rule: Remove propTypes and defaultProps declarations after adding TypeScript types. Keeping both creates a maintenance burden with no added safety.
Setup
If not already on TypeScript, see the JavaScript → TypeScript playbook first. Then:
npm install --save-dev @types/react @types/react-dom
npm uninstall prop-types
Core transformations
Basic props
// PropTypes
import PropTypes from 'prop-types';
function Button({ label, onClick, disabled }) {
return <button onClick={onClick} disabled={disabled}>{label}</button>;
}
Button.propTypes = {
label: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
disabled: PropTypes.bool,
};
Button.defaultProps = {
disabled: false,
};
// TypeScript
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}
function Button({ label, onClick, disabled = false }: ButtonProps) {
return <button onClick={onClick} disabled={disabled}>{label}</button>;
}
Enum / oneOf
// PropTypes
Component.propTypes = {
variant: PropTypes.oneOf(['primary', 'secondary', 'danger']),
};
// TypeScript
interface Props {
variant?: 'primary' | 'secondary' | 'danger';
}
Shape / nested objects
// PropTypes
Component.propTypes = {
user: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
email: PropTypes.string,
}).isRequired,
};
// TypeScript
interface User {
id: string;
name: string;
email?: string;
}
interface Props {
user: User;
}
Arrays
// PropTypes
Component.propTypes = {
items: PropTypes.arrayOf(PropTypes.string).isRequired,
users: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string })),
};
// TypeScript
interface Props {
items: string[];
users?: Array<{ id: string }>;
}
Children
// PropTypes
Component.propTypes = {
children: PropTypes.node.isRequired,
child: PropTypes.element.isRequired,
};
// TypeScript
interface Props {
children: React.ReactNode;
child: React.ReactElement;
}
Functions with signatures
// PropTypes
Component.propTypes = {
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func,
};
// TypeScript — be explicit about the signature
interface Props {
onChange: (value: string) => void;
onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;
}
instanceOf / custom validators
// PropTypes
Component.propTypes = {
date: PropTypes.instanceOf(Date),
custom: PropTypes.custom, // custom validator function
};
// TypeScript
interface Props {
date?: Date;
custom?: string; // replace with the actual type
}
defaultProps → default parameters
// PropTypes defaultProps
Component.defaultProps = {
size: 'medium',
disabled: false,
};
// TypeScript — default values in destructuring
function Component({ size = 'medium', disabled = false }: Props) { ... }
PropTypes → TypeScript type mapping
| PropTypes | TypeScript |
|---|---|
string | string |
number | number |
bool | boolean |
func | () => void (specify signature) |
array | unknown[] (specify element type) |
object | Record<string, unknown> (define interface) |
node | React.ReactNode |
element | React.ReactElement |
symbol | symbol |
any | unknown (avoid any) |
oneOf([...]) | union type 'a' | 'b' |
oneOfType([...]) | union type string | number |
arrayOf(T) | T[] |
shape({...}) | interface or inline object type |
instanceOf(C) | InstanceType<typeof C> |
.isRequired | remove ? (required by default) |
| absent (optional) | add ? to property |
When NOT to migrate
- Project not on TypeScript yet — do
JavaScript → TypeScriptfirst. - Library shipped to JS-only consumers that rely on runtime PropTypes validation.
- React ≤ 18 where PropTypes still warns usefully — React 19 removed it, so this concern fades.
Pitfalls
isRequiredinverts — in PropTypes, all props are optional unless.isRequiredis added. In TypeScript, all props are required unless marked with?.PropTypes.funcloses signature info — always replace with an explicit function type.PropTypes.objectandPropTypes.arrayare too loose — replace with specific interfaces orunknown[].defaultPropsis deprecated in React 19 — always use default parameter values instead.- Class components with
defaultProps— if still on class components, move defaults into the constructor or use the static property pattern with TypeScript. - Third-party components with PropTypes —
@types/*packages already define TypeScript types; no migration needed for those.
Validation checklist
- No
import PropTypes from 'prop-types'remaining - No
Component.propTypes = {...}declarations - No
Component.defaultProps = {...}— replaced with parameter defaults - Each component has a typed props interface/type
-
prop-typesremoved frompackage.json -
tsc --noEmitreports no prop-related errors
Codemod references
- react-javascript-to-typescript-transform — Lyft’s codemod handles PropTypes → TS in many cases.
- Manual review required where PropTypes used
oneOfType/shapewith non-trivial nesting.
AI Prompt
You are migrating a React component from PropTypes to TypeScript.
Rules:
1. Create an interface (or type) for the component's props, named <ComponentName>Props.
2. Map each propTypes entry to its TypeScript equivalent using this table:
- string → string, number → number, bool → boolean
- func → explicit function signature (e.g. () => void or (val: string) => void)
- node → React.ReactNode, element → React.ReactElement
- oneOf([...]) → union type ('a' | 'b')
- shape({...}) → inline object type or named interface
- arrayOf(T) → T[]
- .isRequired → required prop (no ?); absent → optional prop (?)
3. Replace defaultProps with default parameter values in the function signature.
4. Remove the propTypes and defaultProps declarations entirely.
5. Do not change JSX or component logic.
Migrate the following component: