React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

typescriptreacttsconfigsetupconfiguration

TypeScript Basics for React

Recipe

Set up a TypeScript-powered React project with a well-configured tsconfig.json, understand how .tsx files work, and learn the core type annotations every React developer needs.

Working Example

// tsconfig.json (Next.js 15 / React 19 recommended)
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
// src/components/Greeting.tsx
type GreetingProps = {
  name: string;
  age?: number;
};
 
export function Greeting({ name, age }: GreetingProps) {
  return (
    <div>
      <h1>Hello, {name}</h1>
      {age !== undefined && <p>Age: {age}</p>}
    </div>
  );
}
// Usage
<Greeting name="Alice" />
<Greeting name="Bob" age={30} />

Deep Dive

How It Works

  • TypeScript adds static type checking on top of JavaScript. The compiler catches type errors before your code runs in the browser.
  • .tsx files are TypeScript files that support JSX syntax. The jsx: "preserve" setting tells TypeScript to leave JSX untouched for the bundler (Next.js, Vite) to handle.
  • strict: true enables a family of strict checks (strictNullChecks, strictFunctionTypes, noImplicitAny, etc.) that catch the most bugs.
  • moduleResolution: "bundler" matches how modern bundlers resolve imports, supporting package.json exports fields and extensionless imports.
  • Path aliases like @/* let you write import { Button } from "@/components/Button" instead of fragile relative paths.

Variations

Vite + React setup:

{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2023", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "noEmit": true,
    "isolatedModules": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

Adding stricter options incrementally:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noFallthroughCasesInSwitch": true
  }
}

TypeScript Notes

  • Always install React types: npm install -D @types/react @types/react-dom. With React 19, types ship with react and @types/react may not be needed depending on your setup.
  • Use type for props and simple shapes; use interface when you need declaration merging or extends.
  • Prefer unknown over any. If you must use any, add a // eslint-disable comment and a TODO to fix it later.

Gotchas

  • Forgetting "strict": true means TypeScript will not flag null or undefined access, defeating much of its value.
  • Setting jsx to "react" instead of "preserve" or "react-jsx" forces you to import React from "react" in every file.
  • skipLibCheck: true is recommended for build speed but can hide type errors in your own .d.ts files.
  • Using any silently disables type checking for everything that value touches downstream.

Alternatives

ApproachProsCons
strict: true from day oneCatches the most bugs earlySteeper learning curve for beginners
Gradual adoption (strict: false)Easier migration from JSMisses critical null/undefined bugs
JSDoc types (no .ts files)Zero build step changesVerbose, limited type expressiveness
interface for all propsDeclaration merging, familiar OOP styleCannot express unions or mapped types
type for all propsUnions, intersections, mapped typesNo declaration merging

FAQs

Why is strict: true recommended from day one in a React project?
  • It enables strictNullChecks, noImplicitAny, strictFunctionTypes, and other checks as a group.
  • Catches null/undefined access bugs that would otherwise surface only at runtime.
  • Adding strict mode later to a large codebase requires fixing hundreds of errors at once.
What does jsx: "preserve" do and when should you use a different setting?
  • "preserve" leaves JSX untouched so the bundler (Next.js, Vite) handles the transformation.
  • Use "react-jsx" for Vite + React projects that use the automatic JSX runtime.
  • Use "react" only if you need the classic React.createElement transform (requires importing React in every file).
When should you use type vs interface for component props?
  • Use type when you need unions, intersections, or mapped types.
  • Use interface when you need declaration merging or extends.
  • For most React props, type is sufficient and more flexible.
What is the purpose of moduleResolution: "bundler" in tsconfig?
  • It matches how modern bundlers like Next.js and Vite resolve imports.
  • Supports package.json exports fields and extensionless imports.
  • Replaces older options like "node" or "node16" for bundled projects.
Do you still need to install @types/react with React 19?
  • React 19 ships its own types, so @types/react may not be needed.
  • Check your setup -- some configurations still require the separate package.
  • When in doubt, install it as a dev dependency and let TypeScript resolve conflicts.
What is the difference between .ts and .tsx files?
  • .tsx files support JSX syntax in addition to TypeScript.
  • .ts files are for pure TypeScript logic with no JSX.
  • Use .tsx for any file that returns or contains JSX elements.
Gotcha: What happens if you set skipLibCheck: true in your tsconfig?
  • TypeScript skips type-checking all .d.ts files, including your own custom declaration files.
  • This speeds up builds but can hide type errors in your declarations.
  • Errors in third-party .d.ts files are also silently ignored.
Why does using any defeat the purpose of TypeScript?
  • any silently disables type checking for the value and everything it touches downstream.
  • Prefer unknown -- it forces you to narrow or validate before accessing properties.
  • If you must use any, add a comment explaining why and a TODO to fix it later.
What does the paths alias @/* do in tsconfig?
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
  • Maps @/components/Button to ./src/components/Button.
  • Eliminates fragile relative paths like ../../../components/Button.
How do noUncheckedIndexedAccess and exactOptionalPropertyTypes add extra safety beyond strict: true?
  • noUncheckedIndexedAccess adds | undefined to array and record index access, catching out-of-bounds bugs.
  • exactOptionalPropertyTypes distinguishes between a missing property and one set to undefined.
  • Neither is included in strict: true -- they must be enabled separately.
What is the correct way to type an optional prop with a default value?
type GreetingProps = {
  name: string;
  age?: number; // optional
};
 
function Greeting({ name, age = 25 }: GreetingProps) {
  return <p>{name} is {age} years old</p>;
}
  • Mark the prop as optional with ? in the type.
  • Provide the default in the destructuring pattern.
  • TypeScript narrows the type inside the function body to exclude undefined.