TypeScript with React
Typing props, children, state, events, refs, context, reducers and generic components.
TypeScript adds types to JavaScript, and it pairs brilliantly with React: misspelled props, missing props, wrong event types and undefined crashes get caught in your editor instead of in production. Most professional React codebases use TypeScript, and the Vite react-ts template sets it up for you. This lesson covers the patterns you'll use every day.
Setup#
Component files use the .tsx extension. The template includes a strict tsconfig and the React type definitions (@types/react, @types/react-dom). Vite strips types when serving your app (it doesn't type-check), so run the compiler to check — the template's build script does this:
Tip: your editor (VS Code) type-checks as you type, which is where TypeScript is most helpful.
Typing props#
Describe props with a type (or interface) and annotate the parameter:
?makes a prop optional; give it a default with destructuring.- String literal unions (
"primary" | "secondary") give you autocomplete and catch typos. - You don't need to annotate the return type — TypeScript infers it.
children and other JSX props#
Use ReactNode for anything renderable. Use React.JSX.Element only when you need specifically one element.
Extending native element props#
A custom Input should accept everything a real <input> does. Use ComponentProps:
ComponentProps<"input"> includes ref too, so in React 19 your component can receive and forward a ref without forwardRef.
State#
useState infers the type from the initial value. Add a type argument when the initial value doesn't tell the whole story:
Tip:
res.json()returnsany— TypeScript can't know what the server sends.as Useris a promise you make. For untrusted data, validate at runtime with a library like Zod.
Events#
Inline handlers are typed automatically. When you extract a handler, use React's event types:
Don't remember a type? Write the handler inline, hover over e in your editor, and copy the type. (Use SubmitEvent for form submissions — the older FormEvent type is deprecated.)
Refs#
Reducers with discriminated unions#
The never trick in default is called exhaustiveness checking: add a new action type and TypeScript points at every reducer that doesn't handle it.
Context with a safe custom hook#
Consumers call useAuth() and get a non-null value — no ?. everywhere.
Generic components#
A component can be generic, just like a function. This List works with any item type and keeps renderItem type-safe:
Typing custom hooks#
Return tuples with as const so each position keeps its own type:
Without as const, the return type would be (boolean | (() => void))[], and open couldn't be used as a boolean.
Useful type helpers#
Common mistakes#
- Using
anyto silence errors — it switches type checking off. Useunknownand narrow, or fix the type. - Typing components as
React.FC— it works, but a plain annotated function is simpler and the community default. useRef<HTMLInputElement>()withoutnull(an error in React 19's types — pass an initial value).- Assuming
as SomeTypevalidates data. It doesn't; it only tells the compiler to trust you. - Ignoring red squiggles because "Vite still runs it" — Vite doesn't type-check; run
tsc.
What's next#
Types catch one class of bugs; tests catch the rest. Next: testing React components with Vitest and React Testing Library.
Check your understanding
Quick quiz
1.What's the right type for a
childrenprop that can hold any renderable content?2.How should you type
const inputRef = useRef(...)for a DOM input?3.Why use a discriminated union for reducer actions, like
{ type: 'added'; text: string } | { type: 'deleted'; id: number }?
Finished reading?
Mark this lesson complete to track your progress.