Skip to main content

Overview

TypeScript adds static typing to JavaScript, helping you catch errors early and improve code quality. This guide covers using TypeScript with React components.
TypeScript provides autocomplete, type checking, and better refactoring support in your React projects.

TypeScript Basics

Primitive Types

TypeScript includes basic types for JavaScript primitives:
// Primitives
let age: number;
age = 12;

let userName: string;
userName = 'Max';

let isInstructor: boolean;
isInstructor = true;

Arrays and Objects

// Array of strings
let hobbies: string[];
hobbies = ['Sports', 'Cooking'];

// Object type
let person: {
  name: string;
  age: number;
};

person = {
  name: 'Max',
  age: 32
};

// Array of objects
let people: {
  name: string;
  age: number;
}[];
TypeScript can often infer types automatically, so you don’t always need to specify them explicitly.

Union Types

Union types allow a variable to hold multiple types:
let userName: string | string[];
userName = 'Max';  // OK
userName = ['Max', 'Anna'];  // Also OK

let course: string | number = 'React - The Complete Guide';
course = 12341;  // OK

Flexibility

Union types provide flexibility while maintaining type safety.

Type Guards

TypeScript can narrow types based on runtime checks.

Type Aliases

Create reusable type definitions with type aliases:
type Person = {
  name: string;
  age: number;
};

let person: Person;
person = {
  name: 'Max',
  age: 32
};

let people: Person[];
Type aliases improve code readability and make complex types easier to maintain.

Generics

Generics enable type-safe reusable functions:
function insertAtBeginning<T>(array: T[], value: T) {
  const newArray = [value, ...array];
  return newArray;
}

const demoArray = [1, 2, 3];
const updatedArray = insertAtBeginning(demoArray, -1); // [-1, 1, 2, 3]

const stringArray = insertAtBeginning(['a', 'b', 'c'], 'd');
// TypeScript knows this is string[]
Generics provide:
  • Type safety without sacrificing flexibility
  • Better autocomplete and IntelliSense
  • Compile-time error detection
  • Reusable, type-safe functions
Use generics when:
  • Creating reusable utility functions
  • Working with arrays or collections
  • Building components that work with various data types
  • Defining function or component props that vary

React Components with TypeScript

Typing Props

Define prop types using TypeScript interfaces or inline types:
import React from 'react';

const Todos: React.FC<{ items: string[] }> = (props) => {
  return (
    <ul>
      {props.items.map((item) => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
};

export default Todos;
1

Define the component type

Use React.FC<PropsType> to define a functional component with typed props.
2

Specify prop types

Provide an object type or interface describing the component’s props.
3

Access props with autocomplete

TypeScript provides IntelliSense for all prop names and types.

Using Custom Classes

Create model classes with TypeScript:
class Todo {
  id: string;
  text: string;

  constructor(todoText: string) {
    this.text = todoText;
    this.id = new Date().toISOString();
  }
}

export default Todo;

Working with Refs

Type refs using generic parameters:
import { useRef } from 'react';

const NewTodo: React.FC = () => {
  const todoTextInputRef = useRef<HTMLInputElement>(null);

  const submitHandler = (event: React.FormEvent) => {
    event.preventDefault();
    
    // Non-null assertion operator (!)
    const enteredText = todoTextInputRef.current!.value;
    
    if (enteredText.trim().length === 0) {
      return;
    }
  };

  return (
    <form onSubmit={submitHandler}>
      <input type='text' ref={todoTextInputRef} />
      <button>Add Todo</button>
    </form>
  );
};
The ! operator tells TypeScript that the value will definitely exist. Use carefully to avoid runtime errors.

Typing State

Use generics to type useState:
import { useState } from 'react';
import Todo from './models/todo';

function App() {
  const [todos, setTodos] = useState<Todo[]>([]);

  const addTodoHandler = (todoText: string) => {
    const newTodo = new Todo(todoText);
    
    setTodos((prevTodos) => {
      return prevTodos.concat(newTodo);
    });
  };

  return (
    <div>
      <NewTodo onAddTodo={addTodoHandler} />
      <Todos items={todos} />
    </div>
  );
}

Type Inference

TypeScript can often infer state types from initial values.

Generic Types

Use generics for complex state like arrays of objects.

Context API with TypeScript

Type Context using interfaces:
import React, { useState } from 'react';
import Todo from '../models/todo';

type TodosContextObj = {
  items: Todo[];
  addTodo: (text: string) => void;
  removeTodo: (id: string) => void;
};

export const TodosContext = React.createContext<TodosContextObj>({
  items: [],
  addTodo: () => {},
  removeTodo: (id: string) => {},
});

const TodosContextProvider: React.FC<{ children: React.ReactNode }> = (props) => {
  const [todos, setTodos] = useState<Todo[]>([]);

  const addTodoHandler = (todoText: string) => {
    const newTodo = new Todo(todoText);
    setTodos((prevTodos) => prevTodos.concat(newTodo));
  };

  const removeTodoHandler = (todoId: string) => {
    setTodos((prevTodos) => prevTodos.filter(todo => todo.id !== todoId));
  };

  const contextValue: TodosContextObj = {
    items: todos,
    addTodo: addTodoHandler,
    removeTodo: removeTodoHandler,
  };

  return (
    <TodosContext.Provider value={contextValue}>
      {props.children}
    </TodosContext.Provider>
  );
};

export default TodosContextProvider;
1

Define context type

Create a type describing the context value shape.
2

Create typed context

Pass the type as a generic parameter to createContext().
3

Use in components

TypeScript now provides autocomplete for context values.

Event Types

Common React event types in TypeScript:

Form Events

const handleSubmit = (
  event: React.FormEvent
) => {
  event.preventDefault();
};

Click Events

const handleClick = (
  event: React.MouseEvent
) => {
  console.log('Clicked!');
};

Change Events

const handleChange = (
  event: React.ChangeEvent<HTMLInputElement>
) => {
  setValue(event.target.value);
};

Keyboard Events

const handleKeyPress = (
  event: React.KeyboardEvent
) => {
  if (event.key === 'Enter') {
    submit();
  }
};

Best Practices

Let TypeScript infer types from initial values:
// Type is inferred as number
const count = 0;

// Explicit type needed for empty arrays
const items: string[] = [];
Use interfaces for object shapes, especially for props:
interface TodoProps {
  id: string;
  text: string;
  onRemove: (id: string) => void;
}

const Todo: React.FC<TodoProps> = (props) => { /* ... */ };
Use strict TypeScript settings in tsconfig.json:
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}
Instead of any, use:
  • unknown for truly unknown types
  • Generics for flexible but type-safe code
  • Union types for multiple specific types
// Bad
function process(data: any) { }

// Good
function process<T>(data: T) { }

Common Patterns

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

const Container: React.FC<ContainerProps> = ({ children, title }) => {
  return (
    <div>
      <h2>{title}</h2>
      {children}
    </div>
  );
};

TypeScript + React Cheat Sheet

interface MyComponentProps {
  name: string;
  age: number;
  onSave: (name: string) => void;
}

const MyComponent: React.FC<MyComponentProps> = (props) => {
  // Component implementation
};

Next Steps

Testing

Learn how to test React components with TypeScript

Advanced Types

Explore utility types, conditional types, and mapped types

Build docs developers (and LLMs) love