JavaScript Resources

React with TypeScript: A Perfect Match?

React with TypeScript: A Perfect Match?

Type errors show up in your editor instead of in a bug report. That is what compile time type checking gets you when TypeScript sits on top of React’s component model, and it is most of what changes day to day.

Meta maintains React. Microsoft maintains TypeScript. Nobody ships the two as a single package, so the pairing happens at the project level, in your config. Teams building production web apps use it to check component props, state, and hook return values against explicit types before any of it reaches a user.

Adoption tells the story better than any argument does. TypeScript overtook Python and JavaScript to become the most used language on GitHub in August 2025, a shift GitHub ties partly to major frameworks scaffolding new projects in TypeScript by default (GitHub, Octoverse 2025 report).

What Is React with TypeScript?

Nothing about the browser output changes. React renders the same DOM it always did. What changes is the layer sitting between you and that output, since the compiler and your editor now know what every component expects before the code runs anywhere.

TypeScript was designed at Microsoft under Anders Hejlsberg, the same engineer behind C# and Turbo Pascal.

React came out of Meta (then Facebook) and stayed a plain JavaScript library. Putting the two together means writing components inside .tsx files, a file extension that tells the compiler JSX is allowed alongside type annotations.

All of this lives inside the wider practice of front-end development, where the browser-facing half of an app gets built and typed.

Half-typed projects leak “any” everywhere, which is why most teams add types across the whole codebase instead of a handful of files.

It gets mistaken for a few things it isn’t.

  • Not a separate framework or a fork of React
  • Not a rewrite of React’s runtime behavior
  • Not mandatory. Plain JavaScript React still works fine

VS Code is itself written largely in TypeScript, and Microsoft was the first heavy user of its own compiler. That history explains a lot about why autocomplete and inline errors feel so tightly wired into a typed React project.

Why is JavaScript everywhere?

Uncover JavaScript statistics: universal adoption, framework diversity, full-stack dominance, and the language that runs the modern web.

Discover JS Insights →

Why Use TypeScript in React Instead of Plain JavaScript?

Prop mismatches, missing fields, wrong argument types. TypeScript flags all of those while the component is still open in front of you, rather than after somebody hits the bug in production.

That shift changes how teams plan React work and how they hand it between engineers. A reviewer can trust the shapes and spend their attention on the logic instead.

The research here is thinner than the marketing implies, though it does exist.

  • 40% of developers now write exclusively in TypeScript, up from 34% in 2024 and 28% in 2022 (State of JavaScript 2025 survey, Devographics, published 2026)
  • 15% of real-world JavaScript bugs could have been caught by a static type system like TypeScript, based on commit history across open source projects (Gao, Bird and Barr, University College London and Microsoft, ICSE 2017)
  • 38% of production bugs at Airbnb could have been prevented by TypeScript, according to a postmortem analysis presented by engineer Brie Bunge at JSConf Hawaii, 2019

Airbnb’s number came out of a long code refactoring effort, months of converting existing JavaScript files and tracing old bug reports back to what would have been a type error.

Bugs don’t disappear, obviously. Logic errors, bad API responses, and race conditions all still slip through, because TypeScript only checks the shape of your data and has no opinion on what your code decides to do with it.

How Do You Type React Component Props?

YouTube player

Interface vs Type for Component Props

Both work for typing props. The difference only shows up at the edges.

An interface can be extended by another interface, and it merges automatically if you declare it twice in the same scope. A type alias handles unions, tuples, and mapped types that an interface cannot express on its own.

So an interface fits a plain prop shape you expect other components to extend, and a type alias fits props that can arrive in more than one distinct shape. Pick one convention per project and stick with it. Mixed conventions in the same repo cause more confusion than either choice does on its own.

The community-maintained TypeScript cheat sheet is a fast way to check exact syntax when this distinction gets fuzzy mid-project.

Typing Children and Render Props

Children need their own type. React ships ReactNode for anything renderable, and PropsWithChildren as a shortcut wrapper around a props type you already wrote.

Looking to level up your React skills? Every hook, method, and syntax shorthand you need - including useState, useEffect, and JSX patterns - is on one page in the React Cheat Sheet.

  • Typing children as ReactNode covers text, elements, arrays, and null
  • Wrapping in PropsWithChildren<Props> adds a children field to an existing prop type without writing it twice
  • A function-as-child gets typed as a function returning ReactNode instead of as an element

Render props follow the same idea. The prop itself is a function type, and its return type is whatever JSX that function is expected to hand back.

The React cheat sheet covers a wider set of prop patterns, worth bookmarking once children and render props start feeling repetitive.

Shopify’s Polaris component library, open sourced on GitHub, types nearly all of its React components in TypeScript to keep prop shapes consistent across a large group of contributors.

React.FC vs Plain Function Components: Which Should You Use?

YouTube player

Most React with TypeScript codebases now skip React.FC and type props directly on a plain function.

It was never required. React.FC was a convenience type that quietly added a children prop, even to components that never accepted one.

ApproachProsCons
React.FCAdds return type checking automatically, familiar to older tutorialsImplicit children prop, awkward with generics, extra ceremony for no real gain
Plain functionSimpler syntax, plays well with generic components, matches current React typesNo automatic return type check, relies on inference for the return value

Create React App’s official TypeScript template dropped React.FC from its generated code in January 2020, and the wider community followed.

This split is one of the first things that comes up during a code review process on a new project, since it signals which convention the rest of the files follow.

Newer TypeScript versions and current React types have mostly closed the gap between the two. At this point it is a style call.

How Do You Type React Hooks?

YouTube player

Typing Built-in Hooks

TypeScript infers most hook types automatically from the value you pass in, so a lot of hook code needs no annotation at all.

HookTyping approach
useStateInferred from the initial value, or set explicitly with useState<Type>()
useReducerA discriminated union for the action type, matched against the state type
useContextInherits the type from the context object’s default value
useRefTyped by the ref target, with null included for DOM refs before mount

If the hooks themselves still feel unfamiliar, the React hooks explained guide is a good companion to read alongside this.

Looking to sharpen your JavaScript skills? Array methods, ES6+ syntax, promises, and everything else you need - including async/await, destructuring, and arrow functions - is on one page in the JavaScript Cheat Sheet.

Typing Custom Hooks

A custom hook is just a function. Normal TypeScript function rules apply to its parameters and its return value, nothing special.

  • Type the parameters the way you would type any other function argument
  • Type the return value explicitly once it returns more than one piece of state
  • Return a tuple or an object depending on how callers will destructure it

Leaving the return type off usually still works, since TypeScript infers it. Writing it out makes refactors safer later, which is the actual reason to bother.

How Do You Type Event Handlers in React?

Each handler wants a specific event type, and the element goes in the generic.

  • onClick takes React.MouseEvent<HTMLButtonElement>, or whatever element the handler actually sits on
  • onChange takes React.ChangeEvent<HTMLInputElement> for text fields, and HTMLSelectElement for dropdowns
  • onSubmit takes React.FormEvent<HTMLFormElement>, usually paired with a call to preventDefault

These are all versions of React’s SyntheticEvent, the wrapper React builds around the browser’s native event object so behavior stays consistent across browsers.

A native DOM event type, plain MouseEvent from the browser API, won’t match what React hands your handler. Mixing those two up is one of the more common errors newcomers run into.

Getting the element generic wrong doesn’t always throw right away, which is the annoying part. It just leaves properties like event.target.value untyped, and that turns debugging javascript issues later into a bigger chore than it needed to be.

Which Tool Should You Use to Start a React TypeScript Project?

Vite is the default for most new React with TypeScript projects that don’t need server rendering. Next.js takes over when routing, server rendering, or a built-in API layer is part of the plan from day one.

ToolBest forTypeScript setupMaintained by
ViteSPAs, dashboards, component librariesTypeScript template included by defaultEvan You and open source contributors
Next.jsServer-rendered apps, full-stack projectsTypeScript template included by defaultVercel
Create React AppLegacy projects, older tutorialsTypeScript template available, project unmaintainedCommunity, largely inactive

Vite carries a 98% satisfaction score against Webpack’s 26% in the 2025 State of JavaScript survey, even though Webpack still edges it out on raw usage share.

The choice comes down to whether the project needs a server at all, not to which tool “supports” TypeScript better. All three do. Create React App is the one to avoid outright on anything new, since nobody is actively maintaining it anymore.

Sorting out the right tech stack for web development before scaffolding anything saves a migration later, especially once real users are on it.

How Do You Set Up a React TypeScript Project?

With a starter tool already picked, the setup itself is short. Skip a step and type checking either doesn’t run at all, or runs against settings you never meant to use.

  1. Scaffold the project from the tool’s official TypeScript template
  2. Install @types/react and @types/react-dom as dev dependencies
  3. Turn on strict mode in tsconfig.json
  4. Add typescript-eslint so lint catches type issues, not just style ones
  5. Wire a standalone type-check script into the build pipeline, separate from the bundler’s own transpile step

Those type definitions get pulled a staggering number of times. @types/react sees more than 125 million weekly downloads on npm (npm trends, 2026), which gives some sense of how many projects run through this exact setup.

Configuring tsconfig.json for React

Four settings carry most of the weight here.

  • strict turns on the full set of checks, including no implicit any
  • jsx set to react-jsx means files don’t need to import React just to use JSX
  • moduleResolution goes to bundler with Vite, or node16 for Node-based tooling
  • skipLibCheck skips type checking inside installed .d.ts files, which keeps builds fast

Build speed has shifted a lot lately. Microsoft’s native port of the TypeScript compiler, written in Go instead of running on Node.js, cut type-checking time on VS Code’s 1.5 million line codebase from 77.8 seconds to 7.5 seconds (Anders Hejlsberg, Microsoft TypeScript team, March 2025).

Enforcing Types with ESLint

ESLint on its own only checks style and obvious mistakes. Type-aware rules need the typescript-eslint parser plugged in separately, which is a step plenty of teams forget and then wonder why nothing gets flagged.

RuleWhat it catches
no-explicit-anyFlags any used as an escape hatch instead of a real type
no-floating-promisesCatches a promise that’s created but never awaited or handled
consistent-type-importsForces type-only imports to use import type

Most teams treat linting in programming as a pre-commit gate rather than something developers remember to run by hand, so these rules catch problems before a pull request even opens.

How Do You Migrate an Existing React App to TypeScript?

File by file works better than all at once, unless the codebase is small enough to finish in a single sitting.

Stripe went the other way on purpose. It converted more than 3.7 million lines of its dashboard codebase from Flow to TypeScript in a single pull request, choosing a full cutover instead of running two type systems side by side (Stripe Engineering Blog, 2022).

That took tooling investment most teams don’t have sitting around, so the gradual path stays the realistic default.

  1. Add TypeScript to the project without touching existing JavaScript files
  2. Turn on allowJs so .js and .tsx files can sit side by side
  3. Turn on checkJs to get light type checking on the JavaScript files still in place
  4. Rename files to .tsx starting with the ones fewest other files depend on
  5. Add a stub declaration file for any untyped package that blocks the build

It mirrors incremental software development more broadly. Small shippable steps, no single risky rewrite.

Renaming a file doesn’t finish the job. Every any that TypeScript inferred on the way through still needs a real type eventually, or the migration has just moved the JavaScript problem into a .tsx file.

Which React Libraries Need Extra Type Setup?

Some libraries ship their own types. Others want a separate types package, or generate types from a schema at build time.

LibraryTyping statusNote
Redux ToolkitShips its own typesOfficial Redux team recommendation for new stores
React RouterShips its own typesRoute params typed through generics since v6
ZodShips its own typesInfers a TypeScript type directly from a runtime schema
React Hook FormShips its own typesOften paired with Zod for validation

Schema-first libraries

Zod and its cousins define the shape once and derive the TypeScript type from it, so a type and its runtime check can’t drift apart over time. The tradeoff is a small runtime cost, since validation genuinely runs on every parse.

Codegen-based libraries

GraphQL Code Generator writes TypeScript types from a schema or a set of queries, which keeps frontend types in sync with backend changes without anyone remembering to do it. The cost is a build step that has to run before the app compiles cleanly.

tRPC skips generation altogether. It shares types straight from a Node backend to a React frontend through plain TypeScript inference, which is how the API integration layer stays typed with no codegen step in the middle.

How Do You Fix Common TypeScript Errors in React?

Four errors account for most of the noise in a typical React project.

  • A props mismatch means a component received a shape that doesn’t match its interface. Look at the caller, not just the component definition
  • Implicit any shows up when a function parameter has no type and none can be inferred. Add an explicit type, or turn on noImplicitAny to catch these earlier
  • A generic constraint error means a reusable component’s generic type doesn’t satisfy the constraint it was given. Narrow the constraint or widen the generic
  • A missing type declaration means an installed package has no .d.ts file anywhere. Check DefinitelyTyped for an @types package before writing one by hand

Most of these appear the moment a file is saved, not after a full build.

The generic constraint one trips people up longest, mostly because the error message describes the constraint rather than the actual mistake in the calling code.

Recurring versions of the same error are worth logging somewhere, even a lightweight defect tracking board. A repeated pattern usually points at a gap in the team’s shared types rather than one person being careless.

How Do You Test React Components Written in TypeScript?

Typed tests catch a different class of mistake than the tests themselves are checking for. A test can pass happily while a mocked prop is the wrong shape, unless that mock is typed against the same interface the component uses.

  • Type test utilities and queries like the rest of the codebase, not as an exception
  • Type a mocked hook’s return value against the hook’s real return type
  • Run tsc as a separate check in CI, apart from the test runner

A passing type check and a passing test answer different questions. One confirms the shapes line up. The other confirms the behavior is right.

React Testing Library, the tool most teams reach for here, was built around unit testing principles that favor testing what a user sees over testing a component’s internals.

Its creator, Kent C. Dodds, has argued for years that tests should query the rendered output the way a user would rather than reaching into internals.

Mocking in unit tests gets easier with types in place, since a mismatched mock throws a compile error instead of failing quietly at runtime.

When Does TypeScript Stop Helping in a React Project?

Types vanish at compile time. Nothing about them exists once the code is running in a browser, and that single fact explains most of where TypeScript stops being useful.

  • Untyped third-party data is only as trustworthy as the type you wrote for it. TypeScript trusts that annotation even when the real response looks nothing like it
  • Short-lived prototypes rarely earn back the setup and annotation overhead, since the script gets thrown away in a day
  • Hand-rolled declaration files can be worse than no types at all. A bad .d.ts looks authoritative while describing the wrong shape
  • Any spreading unchecked starts with one untyped dependency or one lazy shortcut and widens quietly through a whole module

None of that is a flaw in TypeScript specifically. It follows from types being a build-time tool layered onto a language that never had them in mind.

JavaScript still has no native type syntax. The TC39 proposal that would add erasable type annotations directly to the language has sat at Stage 1 since it was first raised in 2022, with no committee vote toward Stage 2 as of the most recent update (TC39, proposal-type-annotations repository, 2025).

How much a project gets out of TypeScript against what it costs to set up connects back to software reliability more than to any technical limitation. A tool this useful on large, long-lived apps stops paying for itself the moment the app is neither.

FAQ on React With TypeScript

What is the difference between a .tsx file and a .ts file?

A .tsx file allows JSX syntax inside TypeScript. A .ts file does not.

React components that return markup need .tsx. Plain logic, hooks, and utility functions without any JSX can stay in .ts files.

Is TypeScript required to use React, or is it optional?

TypeScript is optional. React ships as a plain JavaScript library and runs fine without any type system.

Teams add it separately through project configuration, for the compile time checks on props, state, and hooks that plain JavaScript cannot give them.

Can you use TypeScript with React without a bundler like Vite or Webpack?

Partly. Node’s native type stripping, stable since version 24.12, runs TypeScript files directly with no build step.

It strips type annotations only, not JSX, so React components using JSX still need some transform before they reach the browser.

Where Should You Start With React With TypeScript?

Props and state get typed first. Everything else, hooks, event handlers, generic components, tooling choices, comes after, because prop shapes anchor whatever gets built on top of them.

  • Type component props and state first
  • Type hooks and event handlers second
  • Configure tsconfig, linting, and starter tooling last

Order matters here because prop and state types ripple into every hook, handler, and child component that consumes them, while tooling decisions stay reversible on their own.

The tradeoff is that early commits still compile under loose settings, catching fewer mistakes than a project that starts with strict mode already switched on.

Once the type layer holds together, the next gap worth closing is runtime behavior, which is the subject of React performance optimization.

Bogdan Sandu
Latest posts by Bogdan Sandu (see all)

Stay sharp. Ship better code.

Every week: one curated article, one tool worth knowing, one tip you can use tomorrow. No noise, no padding.