Beginner60 min read

TypeScript Basics for React Developers

Learn TypeScript fundamentals specifically for React development. Understand type annotations, interfaces, and how to add type safety to your React components.

Topics Covered:

TypeScript BasicsType AnnotationsInterfacesReact with TypeScriptProps Typing

Prerequisites:

  • Introduction to React: Your First Component
  • Basic JavaScript knowledge

Video Tutorial

Overview

TypeScript is JavaScript with type safety. It adds static typing to help catch errors during development, provides better IDE support, and makes your code more maintainable. This tutorial covers TypeScript fundamentals specifically for React developers. You'll learn how to type React components, props, state, and events. TypeScript helps prevent bugs, improves developer experience with autocomplete, and makes refactoring safer and easier.

Lesson 1: What is TypeScript and Why Use It?

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. It adds static type checking to catch errors before runtime. Benefits of TypeScript: • Catch errors during development, not in production • Better IDE support (autocomplete, refactoring) • Self-documenting code (types serve as documentation) • Easier refactoring (confidence when changing code) • Better collaboration (clear contracts between components) TypeScript vs JavaScript: • JavaScript: Dynamic typing (types checked at runtime) • TypeScript: Static typing (types checked at compile time) • All valid JavaScript is valid TypeScript • TypeScript compiles to JavaScript Getting Started: • Install TypeScript: `npm install -D typescript @types/react @types/react-dom` • Create tsconfig.json • Rename .js files to .tsx for React components • Start adding types gradually

Code Example:
// JavaScript (no types)
function greet(name) {
  return "Hello, " + name;
}

greet(42); // Works but wrong! Should be string

// TypeScript (with types)
function greet(name: string): string {
  return "Hello, " + name;
}

greet(42); // Error: Argument of type 'number' is not assignable to parameter of type 'string'

// React Component with TypeScript
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean; // Optional prop
}

function Button({ label, onClick, disabled = false }: ButtonProps) {
  return (
    <button onClick={onClick} disabled={disabled}>
      {label}
    </button>
  );
}

// TypeScript catches errors
<Button label="Click me" onClick={() => {}} /> // ✅ Correct
<Button label={123} onClick={() => {}} /> // ❌ Error: label must be string
<Button /> // ❌ Error: label and onClick are required

TypeScript adds type annotations to catch errors before runtime. In React, you type props using interfaces. Optional props use '?' and can have default values.

Lesson 2: Basic TypeScript Types

TypeScript provides several built-in types. Understanding these is fundamental to using TypeScript effectively. Primitive Types: • string: Text data • number: Numbers (both integers and floats) • boolean: true or false • null: Explicitly null • undefined: Not defined • symbol: Unique identifier Type Annotations: • Explicit typing: `let name: string = 'John'` • Type inference: `let name = 'John'` (TypeScript infers string) • Union types: `string | number` (can be either) • Arrays: `string[]` or `Array<string>` • Objects: Define structure with interfaces Any and Unknown: • any: Disables type checking (use sparingly) • unknown: Type-safe version of any Void and Never: • void: No return value (functions that don't return) • never: Function that never returns (throws error or infinite loop)

Code Example:
// Primitive types
let name: string = "Alice";
let age: number = 30;
let isActive: boolean = true;
let data: null = null;
let value: undefined = undefined;

// Type inference (TypeScript guesses the type)
let city = "New York"; // TypeScript knows this is string
// city = 123; // Error: can't assign number to string

// Union types (can be multiple types)
let id: string | number = "abc123";
id = 12345; // Also valid

// Arrays
let fruits: string[] = ["apple", "banana", "orange"];
let numbers: Array<number> = [1, 2, 3, 4, 5];

// Objects with interface
interface User {
  name: string;
  age: number;
  email?: string; // Optional property
}

let user: User = {
  name: "John",
  age: 25,
  // email is optional, so we can omit it
};

// Functions
function add(a: number, b: number): number {
  return a + b;
}

function logMessage(message: string): void {
  console.log(message);
  // No return value
}

function throwError(message: string): never {
  throw new Error(message);
  // Never returns (always throws)

TypeScript has many built-in types. Use type annotations explicitly or let TypeScript infer types. Union types allow multiple types. Interfaces define object shapes.

Lesson 3: Typing React Components

React components in TypeScript need proper typing for props, state, and events. This is where TypeScript shines in React development. Component Types: • Function components: Most common • Props interfaces: Define component props • Event handlers: Type React events • State: Use useState with types Common Patterns: • Interface for props • Generic types for flexible components • Event handler types • Children prop typing • Ref typing

Code Example:
// Typing Function Components

// Props interface
interface CardProps {
  title: string;
  description?: string;
  children?: React.ReactNode;
  onClick?: () => void;
}

function Card({ title, description, children, onClick }: CardProps) {
  return (
    <div onClick={onClick}>
      <h2>{title}</h2>
      {description && <p>{description}</p>}
      {children}
    </div>
  );
}

// Typing state
function Counter() {
  const [count, setCount] = useState<number>(0);
  //                  ^^^^^ Explicit type (optional but good practice)
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

// Typing event handlers
function Form() {
  const [email, setEmail] = useState<string>("");
  
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setEmail(e.target.value);
  };
  
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log(email);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={handleChange}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

// Typing children
interface LayoutProps {
  children: React.ReactNode; // Can be any valid React child
}

function Layout({ children }: LayoutProps) {
  return <div className="layout">{children}</div>;
}

// Typing refs
function InputWithRef() {
  const inputRef = useRef<HTMLInputElement>(null);
  
  const focusInput = () => {
    inputRef.current?.focus(); // Optional chaining for safety
  };
  
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

Type React components with interfaces for props. Type state with useState generics. Type events with React event types. Use React.ReactNode for children. Type refs with the HTML element type.

Lesson 4: Common React TypeScript Patterns

Learn common patterns for typing React features in TypeScript. Patterns Covered: • Optional props • Default props • Event handlers • Conditional rendering types • Form handling • List rendering • Custom hooks typing Best Practices: • Use interfaces for props • Leverage type inference when possible • Use React.ReactNode for children • Type event handlers explicitly • Use union types for variants

Code Example:
// Optional and default props
interface ButtonProps {
  label: string;
  variant?: "primary" | "secondary" | "outline";
  size?: "sm" | "md" | "lg";
  disabled?: boolean;
}

function Button({
  label,
  variant = "primary", // Default value
  size = "md",
  disabled = false,
}: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant} btn-${size}`}
      disabled={disabled}
    >
      {label}
    </button>
  );
}

// Union types for variants
type Status = "loading" | "success" | "error";

interface StatusBadgeProps {
  status: Status;
}

function StatusBadge({ status }: StatusBadgeProps) {
  const colors = {
    loading: "gray",
    success: "green",
    error: "red",
  };
  
  return (
    <span className={`badge badge-${colors[status]}`}>
      {status}
    </span>
  );
}

// Typing custom hooks
function useCounter(initialValue: number = 0) {
  const [count, setCount] = useState<number>(initialValue);
  
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(initialValue);
  
  return { count, increment, decrement, reset };
}

// Using the hook
function App() {
  const { count, increment, decrement } = useCounter(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

// Typing API responses
interface User {
  id: number;
  name: string;
  email: string;
}

function UserProfile({ userId }: { userId: number }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then((res) => res.json())
      .then((data: User) => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  if (!user) return <div>User not found</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Common patterns include optional props with defaults, union types for variants, typing custom hooks, and handling async data with proper types. TypeScript makes these patterns safer and more maintainable.

Conclusion

TypeScript brings type safety to React development, catching errors early and improving developer experience. Start by typing your props with interfaces, then gradually add types to state, events, and functions. Remember: TypeScript is there to help, not to slow you down. Use type inference when types are obvious, and be explicit when it adds clarity. The investment in typing pays off with fewer bugs and easier refactoring.