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 |
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
// 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!Named Export
// 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:
src/
components/
Button.tsx # exports default Button
Modal.tsx # exports default Modal
icons/
CheckIcon.tsx
CloseIcon.tsxSmall, 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:
// 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'// 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'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:
// ui/index.ts
export { default as PrimaryButton } from './Button' // rename on re-export
export { CheckIcon as Icon } from './icons' // rename named exportImporting 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:
// 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 syntax —
import { Button } from './Button'when Button is a default export gives youundefined. Remove the curly braces.Wrong file extension — TypeScript resolvers usually handle this, but be consistent:
.tsxfor files with JSX,.tsfor 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
undefinedat 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
// 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>
}// 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'// 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>
)
}