Course topics

By WebNest Studio Engineering Team

React Tutorial

TypeScript in React

Combining TypeScript with React lets you describe exactly what props a component expects and what shape its state takes, so mistakes like passing a number where a string was expected, or forgetting a required prop entirely, are caught by your editor and the compiler before the app ever runs. This is one of the most common and highest-value uses of TypeScript in professional frontend work, since prop-related bugs are a frequent source of confusing runtime behavior in plain JavaScript React apps.

Why it matters

Most React teams now write TypeScript. Typed props mean that a component used with a missing or misspelled prop fails in the editor instead of in the browser, and they act as documentation that cannot go out of date. Job listings for React roles ask for it almost as often as for React itself.

Typing Component Props with an Interface

You define an interface describing the props a component accepts, then use it as the type of the function's single props parameter: function UserCard({ name, age }: UserCardProps). Optional props are marked with a question mark (age?: number), and any prop not marked optional is required — omitting it is a compile-time error, not a silent undefined discovered later.

Typing useState with a Generic

useState infers its type from the initial value by default (useState(0) infers number), but when the initial value doesn't fully describe the type — commonly when starting from null or an empty array — you supply the type explicitly as a generic: useState<User | null>(null). This tells TypeScript the state will eventually hold a User object, even though it starts out as null, so later code that reads it is checked against the full type.

Why Typed Props Prevent a Whole Class of Bugs

Without types, passing <UserCard age="25" /> (a string) where a number was intended, or forgetting the required name prop entirely, produces no error — the component simply renders incorrectly or displays "undefined" somewhere, and you have to trace the bug back through the component tree. With a typed UserCardProps interface, the exact same mistake is flagged by the editor the moment you write the JSX, with a message naming the offending prop and the expected type.

Example

A typed functional component with a typed useState hook

JavaScript
import { useState } from 'react';

interface User {
  id: number;
  name: string;
}

interface UserCardProps {
  name: string;
  age?: number;
}

function UserCard({ name, age }: UserCardProps) {
  return (
    <div className="card">
      <h3>{name}</h3>
      {age !== undefined && <p>Age: {age}</p>}
    </div>
  );
}

function App() {
  const [user, setUser] = useState<User | null>(null);

  return (
    <div>
      <UserCard name="Asha Patel" age={28} />
      <button onClick={() => setUser({ id: 1, name: 'Ravi Kumar' })}>
        Load User
      </button>
      {user && <p>Loaded: {user.name}</p>}
    </div>
  );
}

// The following would fail to compile:
// <UserCard age={28} />
// Error: Property 'name' is missing in type '{ age: number; }'
// but required in type 'UserCardProps'.
Output
The page renders a card showing "Asha Patel" and "Age: 28," plus a "Load User" button. Clicking it sets state and renders "Loaded: Ravi Kumar" below. The commented-out <UserCard age={28} /> line fails to compile because the required "name" prop is missing, catching the mistake before the app ever runs.

Common Mistakes

  • Typing useState's initial null value with no generic (e.g. "useState(null)"), which infers the type as "null" forever and causes errors the moment you try to assign a real object to that state later.
  • Marking a prop optional (with "?") when the component actually requires it to render correctly, which just hides a bug that will still surface as a runtime error inside the component.
  • Defining the same shape as both a "type" and an "interface" for a prop in different files, causing confusing duplicate-definition or mismatch errors as the codebase grows.
  • Using "any" for props "to make the error go away" instead of fixing the actual shape mismatch, which silently reintroduces the exact class of bug TypeScript was added to prevent.

Exercise

Do this in a Vite project created with the react-ts template, in src/App.tsx. Type the props of the UserCard component with an interface: a required string name, a required number age and an optional boolean isAdmin. In App, type the selected user's state as a string or null.

Expected result: the code compiles; leaving out name, or passing a string as age, is reported as an error in the editor.

JavaScript · starter code
import { useState } from "react";

// TODO: interface UserCardProps

function UserCard({ name, age, isAdmin }) {
  return (
    <p>
      {name}, {age} {isAdmin ? "(admin)" : ""}
    </p>
  );
}

export default function App() {
  // TODO: give this state the type string | null
  const [selected, setSelected] = useState(null);

  return (
    <div onClick={() => setSelected("Asha")}>
      <UserCard name="Asha" age={30} isAdmin />
      <p>Selected: {selected}</p>
    </div>
  );
}
Show hints
  • An optional property is marked with a question mark: isAdmin?: boolean.
  • The type of the state is given as a generic: useState<string | null>(null).
Show solution
JavaScript · solution
import { useState } from "react";

interface UserCardProps {
  name: string;
  age: number;
  isAdmin?: boolean;
}

function UserCard({ name, age, isAdmin }: UserCardProps) {
  return (
    <p>
      {name}, {age} {isAdmin ? "(admin)" : ""}
    </p>
  );
}

export default function App() {
  const [selected, setSelected] = useState<string | null>(null);

  return (
    <div onClick={() => setSelected("Asha")}>
      <UserCard name="Asha" age={30} isAdmin />
      <p>Selected: {selected}</p>
    </div>
  );
}

Quick quiz

1. Which file extension is used for a TypeScript file that contains JSX?

  • A. .ts
  • B. .tsx
  • C. .jsx
  • D. .d.ts
Show answer

B. A plain .ts file cannot contain JSX.

2. How is an optional prop declared in an interface?

  • A. title?: string
  • B. optional title: string
  • C. title: string?
  • D. title = string
Show answer

A. Inside the component its type is then string or undefined.

3. When must the type be given explicitly to useState?

  • A. Always
  • B. Never
  • C. Only for numbers
  • D. When the initial value does not show the full type, such as null or an empty array
Show answer

D. From useState(0), TypeScript infers number without help.

Interview questions

What are the benefits of TypeScript in a React project?

Show answer

Props are checked where a component is used, so a missing, misspelled or wrongly typed prop is an error in the editor and in the build, not a bug at runtime. State, event handlers and API responses are typed, which catches undefined access and mistaken assumptions about data. Editors offer accurate autocompletion, and renaming or restructuring is safe because the compiler finds every affected place.

How do you type an event handler in React?

Show answer

React provides generic event types parameterised by the element. A change handler on an input takes React.ChangeEvent<HTMLInputElement>, a form submit handler takes React.FormEvent<HTMLFormElement>, and a button click takes React.MouseEvent<HTMLButtonElement>. When the handler is written inline in the JSX, TypeScript infers the type and no annotation is needed.

Key Points to Remember

  • Component props are typed with an interface, used as the type annotation on the destructured props parameter.
  • useState infers its type from the initial value, but a generic like useState<User | null>(null) is needed when the initial value alone doesn't describe the eventual type.
  • Typed props turn missing or mismatched props into compile-time errors instead of confusing runtime bugs.
  • TypeScript with React is now the standard combination in professional codebases, precisely because prop-shape bugs are so common in plain JavaScript React code.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Shows a live preview of plain HTML, CSS and JavaScript. JSX needs a React project on your computer. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

These examples need a project environment on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering