TypeScript in React

How to use TypeScript to add static typing to your React components, props, state, and event handlers for safer, more predictable code.

React6 min readConcept 41 of 48

Adding Types to the UI

TypeScript is a superset of JavaScript that adds static types. In the context of React, it allows you to define exactly what "shape" your data should take.

Instead of discovering a bug at runtime because you accidentally passed a number instead of a string to a component, TypeScript catches the error in your editor before you even save the file.

The End of PropTypes

Historically, React developers used a library called PropTypes for runtime type checking. TypeScript completely replaces PropTypes.

TypeScript provides zero-runtime-cost compile-time checking, vastly superior editor auto-completion (IntelliSense), and makes refactoring large codebases infinitely safer.

Typing the Core Concepts

There are four main areas you need to type in a React app:

1. **Props:** Use an interface or type to define what props a component accepts: function Avatar({ url, size }: AvatarProps)

2. **State:** Often inferred, but for complex or initially null state, provide a generic: useState<User | null>(null)

3. **Events:** Use React's built-in event types: (e: React.ChangeEvent<HTMLInputElement>) => void

4. **Refs:** Provide the specific DOM element type: useRef<HTMLDivElement>(null)

JS vs TS Components

Compare a standard JavaScript React component with its TypeScript equivalent. Notice how TypeScript explicitly defines the shape of props, state, events, and refs, providing ultimate editor safety.

UserCard.jsx
// 1. Props: Implicit 'any'. Dangerous!
export default function UserCard({ user, onEdit }) {
// 2. State: Inferred as null, cannot safely set object later.
const [draft, setDraft] = useState(null);
// 3. Refs: No idea what element this attaches to.
const inputRef = useRef(null);
// 4. Events: 'e' is 'any'. No auto-complete for e.target.value.
const handleChange= (e) => {
setDraft(e.target.value);
};
return (
<div>
<h2>{user.name}</h2>
<button onClick={onEdit}>Edit</button>
</div>
);
}
UserCard.tsx
// 1. Props: Explicitly defined via interface.
interface User { name: string; age: number; }
interface UserCardProps{
user: User;
onEdit: () => void;
}
export default function UserCard({ user, onEdit }: UserCardProps) {
// 2. State: Generic typed for future objects.
const [draft, setDraft] = useState<User | null>(null);
// 3. Refs: Specific HTML element type attached.
const inputRef = useRef<HTMLInputElement>(null);
// 4. Events: Typed synthetic event for perfect intellisense.
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// Error caught: setDraft expects User, not string.
setDraft(e.target.value);
};
return (
<div>
<h2>{user.name}</h2>
<button onClick={onEdit}>Edit</button>
</div>
);
}

Side-by-side comparison. Notice how TypeScript forces us to declare exactly what 'user' looks like, what HTML element the ref attaches to, and what kind of event triggers the handler.

Check yourself

Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.

  1. 1What is the recommended way to type the props of a React component?
  2. 2When do you explicitly need to provide a type generic to `useState` (e.g., `useState<number>()`)?
  3. 3What is the correct type to use for a component's `children` prop?

Remember this

  • TypeScript eliminates runtime type errors by catching them in your editor.
  • Type your props using an interface or type.
  • Use React.ReactNode for the children prop.
  • Pass a generic to useState<Type>() if the initial value doesn't provide enough information (like null or []).
  • Use React's built-in event types (like React.MouseEvent) for event handlers.

Done with this concept?

Mark it complete to track your progress. No login needed.