React and TypeScript work together seamlessly. Typed props, state, and hooks catch UI bugs early and improve developer experience.

Create a Typed React Project

Use the TypeScript template with Vite:

  npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev
  

This scaffolds .tsx files and a tsconfig.json configured for React.

Typing Components

Function Components

  interface GreetingProps {
    name: string;
    count?: number;
}

function Greeting({ name, count = 0 }: GreetingProps) {
    return (
        <h1>Hello, {name}! Visits: {count}</h1>
    );
}

export default Greeting;
  

Children

  interface CardProps {
    title: string;
    children: React.ReactNode;
}

function Card({ title, children }: CardProps) {
    return (
        <div className="card">
            <h2>{title}</h2>
            {children}
        </div>
    );
}
  

useState

TypeScript infers state from the initial value:

  const [count, setCount] = useState(0);         // number
const [name, setName] = useState('');          // string
  

Provide an explicit type when the initial value does not cover all cases:

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

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

// Later:
setUser({ id: 1, name: 'Alice' });
  

useRef

DOM refs and mutable values:

  import { useRef, useEffect } from 'react';

function FocusInput() {
    const inputRef = useRef<HTMLInputElement>(null);

    useEffect(() => {
        inputRef.current?.focus();
    }, []);

    return <input ref={inputRef} type="text" />;
}
  

Event Handlers

  function LoginForm() {
    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
    };

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        console.log(e.target.value);
    };

    return (
        <form onSubmit={handleSubmit}>
            <input type="email" onChange={handleChange} />
            <button type="submit">Log in</button>
        </form>
    );
}
  

Custom Hooks

  import { useState, useEffect } from 'react';

interface UseFetchResult<T> {
    data: T | null;
    loading: boolean;
    error: string | null;
}

function useFetch<T>(url: string): UseFetchResult<T> {
    const [data, setData] = useState<T | null>(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<string | null>(null);

    useEffect(() => {
        fetch(url)
            .then(res => res.json())
            .then(setData)
            .catch(err => setError(err.message))
            .finally(() => setLoading(false));
    }, [url]);

    return { data, loading, error };
}
  

Enable "jsx": "react-jsx" in tsconfig.json. Typed React components scale well in large codebases. In the final chapter, we apply TypeScript to Node.js and Express APIs.