Learning how to use TypeScript with React in 2026 is less about heroic configuration and more about knowing where types actually earn their keep. The tooling now does the boring parts for you, so the real skill is typing props, hooks, and events cleanly without drowning your components in annotations. This guide walks the setup, the day-to-day patterns, and the handful of things you can safely ignore.
What changed in 2026
- Create React App is gone. It was officially deprecated, so new projects start with Vite, Next.js, or a framework's own template. The Vite
react-ts starter gives you a working TypeScript setup in one command.
- React 19 simplified the type story.
ref is now a normal prop, so forwardRef boilerplate is fading, and the old React.FC implicit-children behavior is no longer something you need to work around.
- TypeScript 5.x inference got sharper. More prop and hook types resolve on their own, which means less manual annotation and fewer generic gymnastics than a couple of years ago.
- Editors do the heavy lifting. Autocomplete on JSX props and inline error hints catch most mistakes before you ever run a build.
Setting up a project
For a new app, do not fight the config. Scaffold it:
npm create vite@latest my-app -- --template react-ts
That gives you .tsx files, a sensible tsconfig.json, and type-checking in your editor. The one setting worth confirming is "strict": true — it is on by default, and turning it off to "move faster" is a trap.
For an existing JavaScript app, migrate incrementally. Rename files to .tsx one at a time and set allowJs so .js and .ts coexist while you convert.
Typing props
Props are where TypeScript pays off first. Define the shape, destructure, and let inference do the rest:
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'ghost';
}
function Button({ label, onClick, variant = 'primary' }: ButtonProps) {
return <button className={variant} onClick={onClick}>{label}</button>;
}
For children, type them as React.ReactNode — it accepts strings, elements, arrays, and null, which is what JSX actually passes.
The perennial question is type versus interface. Both work. Interfaces extend cleanly; type aliases handle unions better. Pick one default and switch only when the situation demands it.
| Task |
Reach for |
Skip |
| Object-shaped props |
interface or type |
Annotating every return value |
| Union prop values |
type with a literal union |
Loose string types |
| Children |
React.ReactNode |
JSX.Element (too narrow) |
| Component wrapper |
Plain function + props type |
React.FC |
| Reused shapes |
Shared types.ts file |
Inlining the same shape twice |
Typing hooks
useState usually infers from its initial value, so useState(0) is already number. You only annotate when the initial value lies about the real type:
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<Item[]>([]);
useRef needs a type and an initial value — useRef<HTMLInputElement>(null) for DOM refs. useReducer benefits from typed state and action unions, which turns your reducer's switch into an exhaustive, self-checking map. Custom hooks should return a typed tuple or object so callers get full autocomplete.
Typing events and forms
Event handlers are where people flail, because the types are verbose. The trick is to let the JSX attribute infer the handler when you write it inline, and only annotate standalone handlers:
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
Common ones to remember: React.ChangeEvent for inputs, React.FormEvent<HTMLFormElement> for submits, and React.MouseEvent for clicks. If you write the handler inline on the element, TypeScript infers the event for you — no annotation needed.
What to skip
React.FC. It buys you almost nothing in React 19, complicates generic components, and used to add implicit children you did not ask for. A plain function with a typed props argument is cleaner.
any as an escape hatch. Reach for unknown and narrow it, or fix the actual type. Every any is a hole where bugs walk in.
- Over-annotating. If inference already knows a value is a
number, adding : number is noise. Type the boundaries — props, API responses, function signatures — and let the middle infer.
- Typing third-party libs by hand. Check for an
@types/ package or built-in types first; most popular libraries ship them.
FAQ
Do I need to learn TypeScript before React?
No. Learn React's mental model first, then layer TypeScript on. Starting with the react-ts template lets you pick up types gradually as errors appear.
Is type or interface better for props?
Neither is clearly better. Interfaces extend more cleanly; type aliases handle unions well. Consistency inside a project matters more than the choice.
How do I type an API response?
Define an interface for the payload and assert it at the fetch boundary, ideally validated with a runtime schema tool. Do not trust await res.json() to be correctly typed — it returns any.
Does TypeScript slow down my build?
Type-checking adds time, but tools like Vite compile without full checks during dev and run the checker separately, so dev stays fast. Verify build times for your own setup.
Where to go next
If you are wiring your typed React app to a backend or picking infrastructure, keep reading: AWS vs Azure in 2026 covers where to host it, API authentication explained in 2026 covers securing your data calls, and async/await explained in 2026 covers the concurrency patterns behind every typed fetch you write.