ReactImporting & Exporting Components

Importing & Exporting Components

Splitting your application into separate files keeps each piece focused and testable. React components are plain JavaScript values, so they are imported and exported using the same ES Module syntax as everything else in modern JavaScript. Understanding the trade-offs between the two export styles will save you debugging time.

Default Export vs Named Export

JavaScript modules support two kinds of exports. Each has its place, and understanding the difference prevents a frustrating class of import bugs:

Feature

Default Export

Named Export

Syntax (export)

export default function Foo()

export function Foo()

Syntax (import)

import Foo from './Foo'

import { Foo } from './Foo'

Rename on import

Yes — importer chooses the name

Must use as to rename

Per-file limit

One default export per file

Unlimited named exports

IDE autocomplete

Weaker — any name is accepted

Stronger — exact name is required

Refactoring safety

Renaming does not break imports

Renaming must be propagated everywhere

Default Export

TSX
// Button.tsx — default export
export default function Button({ label }: { label: string }) {
  return <button>{label}</button>
}

// App.tsx — importer can choose any name
import Button from './Button'        // ✓ works
import Btn from './Button'           // ✓ also works — different local name
import PrimaryButton from './Button' // ✓ still works — but confusing!
Warning
The freedom to rename a default import is a double-edged sword. It is easy to accidentally import under the wrong name or to have ten different local aliases for the same component across a codebase. Establish a team convention: always import under the name that matches the export.
Named Export

TSX
// icons.tsx — multiple named exports in one file
export function CheckIcon() {
  return <svg aria-hidden="true">{/* ... */}</svg>
}

export function CloseIcon() {
  return <svg aria-hidden="true">{/* ... */}</svg>
}

export function SpinnerIcon() {
  return <svg aria-hidden="true">{/* ... */}</svg>
}

// App.tsx — import only what you need
import { CheckIcon, SpinnerIcon } from './icons'

Named exports are the right choice when a file exports multiple things — icons, utility functions, TypeScript types, constants — because they force the importer to use the exact exported name, which is easier to grep and refactor.

One Component Per File (Convention)

The React community convention is to put one component per file and name the file after the component. This is not enforced by React itself, but it makes navigating a large codebase much easier:

Bash
src/
  components/
    Button.tsx          # exports default Button
    Modal.tsx           # exports default Modal
    icons/
      CheckIcon.tsx
      CloseIcon.tsx

Small, tightly coupled helper components (like a TableRow that is only used by Table) may live in the same file — but the file should still have one primary exported component that gives the file its name.

Barrel Files (index.ts)

A barrel file is an index.ts that re-exports everything from a directory. It gives you a clean public API for a component group and shortens import paths:

TSX
// src/components/ui/index.ts — the barrel file
export { default as Button } from './Button'
export { default as Modal } from './Modal'
export { default as Input } from './Input'
export type { ButtonProps } from './Button'
export type { ModalProps } from './Modal'

TSX
// Without barrel — long individual imports
import Button from '../components/ui/Button'
import Modal from '../components/ui/Modal'
import Input from '../components/ui/Input'

// With barrel — one clean import line
import { Button, Modal, Input } from '../components/ui'
Tip
Barrel files can hurt tree-shaking in some bundler configurations — the bundler may import the entire barrel even if you only use one export. With Vite and webpack 5 (used by modern Next.js), this is usually handled correctly via ESM static analysis, but check your bundle size if you have performance concerns.
Re-Exporting with Rename

You can rename an export as it passes through a barrel file — useful for resolving naming conflicts or creating a more descriptive public API:

TSX
// ui/index.ts
export { default as PrimaryButton } from './Button'   // rename on re-export
export { CheckIcon as Icon } from './icons'           // rename named export
Importing TypeScript Types

When you import a TypeScript interface or type, use the import type syntax. This tells the compiler (and bundlers) the import is type-only and can be erased at compile time, avoiding accidental runtime imports:

TSX
// Always use import type for type-only imports
import type { ButtonProps } from './Button'
import type { ReactNode, FC } from 'react'

// Mix value and type imports in a single statement (TypeScript 4.5+)
import { useState, type Dispatch, type SetStateAction } from 'react'
Common Mistakes
  • Mixing up default and named import syntaximport { Button } from './Button' when Button is a default export gives you undefined. Remove the curly braces.

  • Wrong file extension — TypeScript resolvers usually handle this, but be consistent: .tsx for files with JSX, .ts for pure TypeScript.

  • Circular imports — Component A imports B which imports A. React does not crash, but the circular reference can cause one module to be undefined at startup. Restructure by extracting shared types or logic to a third file.

  • Forgetting to add to the barrel — you create a component but forget to add it to index.ts, then wonder why the import shows as unresolved.

A Complete Module Example

TSX
// src/components/card/CardBody.tsx
export interface CardBodyProps {
  children: React.ReactNode
  padding?: 'sm' | 'md' | 'lg'
}

export default function CardBody({ children, padding = 'md' }: CardBodyProps) {
  return <div className={`card-body p-${padding}`}>{children}</div>
}

TSX
// src/components/card/index.ts  — barrel
export { default as Card } from './Card'
export { default as CardBody } from './CardBody'
export { default as CardHeader } from './CardHeader'
export type { CardBodyProps } from './CardBody'

TSX
// src/pages/Dashboard.tsx
import { Card, CardHeader, CardBody } from '../components/card'

export default function Dashboard() {
  return (
    <Card>
      <CardHeader>Sales Overview</CardHeader>
      <CardBody padding="lg">
        <p>Revenue: $42,000</p>
      </CardBody>
    </Card>
  )
}
Note
Next.js and most modern bundlers support path aliases (e.g. @components/card instead of ../../components/card). Check your tsconfig.json for configured aliases — they make imports cleaner and eliminate the relative-path climbing game.