Skip to content

TypeScript React Components — Complete Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about TypeScript React Components. We cover key concepts, practical examples, and best practices to help you master this topic.

Typing React components with TypeScript gives you compile-time prop validation, autocompletion in JSX, and self-documenting component interfaces — eliminating an entire category of runtime "undefined is not an object" errors.

What You'll Learn

  • Props typing with interfaces
  • FC (FunctionComponent) type
  • Children prop patterns
  • Default props and optional props
  • Generic components

Why It Matters

Without TypeScript, React prop types are checked at runtime (PropTypes) or not at all. TypeScript catches prop mismatches, missing required props, and wrong prop types during development, before they reach users.

Real-World Use

DodaTech's Durga Antivirus Pro dashboard has 200+ typed components. A ScanResultCard component with properly typed props ensures every usage passes threats: string[], status: "clean" | "infected", and timestamp: Date. Wrong prop types are caught at compile time, not when a customer reports a broken dashboard.

Learning Path

flowchart LR
  A[Bundling] --> B[React Components]
  B --> C[React Hooks]
  B --> D[You Are Here]
  C --> E[React Events]
  E --> F[React Context]

Basic Props Interface

import { ReactNode } from 'react';

interface ButtonProps {
  label: string;
  variant?: 'primary' | 'secondary' | 'danger';
  disabled?: boolean;
  onClick: () => void;
  children?: ReactNode;
}

function Button({ label, variant = 'primary', disabled = false, onClick, children }: ButtonProps) {
  const baseClass = 'btn';
  const classes = `${baseClass} ${baseClass}--${variant}`;

  return (
    <button className={classes} disabled={disabled} onClick={onClick}>
      {children || label}
    </button>
  );
}

// Usage
<Button label="Submit" onClick={() => console.log('clicked')}>
  Submit Form
</Button>

The FC Type (Legacy)

Before React 18, the React.FC type was common:

import { FC, ReactNode } from 'react';

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

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

Note: FC implicitly includes children and displayName. Many teams now prefer explicit props without FC.

Children Patterns

interface LayoutProps {
  children: ReactNode;            // Any renderable content
  header?: ReactNode;             // Optional header
  footer?: ReactElement;          // Only a single React element
}

interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map((item, i) => <li key={i}>{renderItem(item, i)}</li>)}</ul>;
}

// Usage
<List items={['Alice', 'Bob']} renderItem={(name) => <span>{name}</span>} />

Default Props

interface AlertProps {
  message: string;
  type?: 'info' | 'warning' | 'error';
  dismissible?: boolean;
  onDismiss?: () => void;
}

function Alert({ message, type = 'info', dismissible = false, onDismiss }: AlertProps) {
  return (
    <div className={`alert alert--${type}`}>
      <span>{message}</span>
      {dismissible && <button onClick={onDismiss}>X</button>}
    </div>
  );
}

Default values at destructuring provide type-safe defaults without runtime overhead.

Generic Components

interface TableProps<T extends Record<string, unknown>> {
  data: T[];
  columns: { key: keyof T; header: string }[];
  onRowClick?: (row: T) => void;
}

function Table<T extends Record<string, unknown>>({ data, columns, onRowClick }: TableProps<T>) {
  return (
    <table>
      <thead>
        <tr>{columns.map(col => <th key={String(col.key)}>{col.header}</th>)}</tr>
      </thead>
      <tbody>
        {data.map((row, i) => (
          <tr key={i} onClick={() => onRowClick?.(row)}>
            {columns.map(col => <td key={String(col.key)}>{String(row[col.key])}</td>)}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

interface User { id: number; name: string; email: string; }
const users: User[] = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
];

<Table data={users} columns={[
  { key: 'id', header: 'ID' },
  { key: 'name', header: 'Name' },
  { key: 'email', header: 'Email' },
]} />

Common Mistakes

1. Not Typing Event Handlers

// Bad — implicit any
function handleClick(e) { }

// Good — typed
function handleClick(e: React.MouseEvent<HTMLButtonElement>) { }

2. Using any for Props

Always define an interface or type for component props.

3. Forgetting Children Type

children can be ReactNode (most flexible) or ReactElement (strict).

4. Not Using Generic Components for Reusable Data Displays

Tables, lists, and selectors should accept generic props.

5. Spreading Props Without Type Safety

// Bad — loses type checking
function BadButton(props: any) { return <button {...props} />; }

// Good — preserves type checking
function GoodButton(props: ButtonProps) { return <button {...props} />; }

Practice Questions

  1. What is ReactNode? A type representing any renderable content: JSX, strings, numbers, fragments, portals, or null.

  2. When would you use a generic component? When the component's data type varies, like a Table or List component.

  3. What does React.FC provide? Implicitly adds children and displayName to the props type. Commonly avoided now for explicitness.

  4. How do you make a prop optional? Add ? to the interface property: variant?: 'primary' | 'secondary'.

Challenge: Create a generic Select component that accepts an array of options with typed values, a selected value, and an onChange callback. Use proper generics so the onChange receives the correct value type.

FAQ

Should I use `React.FC` or plain function components?

Plain functions with explicit props interfaces are more explicit and avoid the implicit children inclusion.

How do I type `children`?

Use ReactNode for any renderable, ReactElement for strict JSX elements, or a specific component type.

Can I use interfaces or types for props? Both work. Interfaces are preferred for public APIs (mergeable), types for unions/computed.
What is `React.ComponentProps`?

A utility type that extracts props from any component: type ButtonProps = React.ComponentProps<typeof Button>;

How do I type `style` prop?

Use React.CSSProperties for inline styles: style?: React.CSSProperties.

Mini Project: Typed Component Library

Create three typed components: Button, Modal, and DataTable.

// Button.tsx
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  loading?: boolean;
  onClick: () => void;
  children: React.ReactNode;
}

export function Button({ variant = 'primary', size = 'md', disabled, loading, onClick, children }: ButtonProps) {
  return (
    <button
      className={`btn btn--${variant} btn--${size}`}
      disabled={disabled || loading}
      onClick={onClick}
    >
      {loading ? 'Loading...' : children}
    </button>
  );
}

What's Next

Now explore typed React hooks:

Lesson Description
{{< ref "/programming-languages/typescript/36-bundling" >}} Review Bundling
{{< ref "/programming-languages/typescript/38-react-hooks" >}} useState, useEffect, useRef with TS
{{< ref "/programming-languages/typescript/39-react-events" >}} Event typing in React

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro