Importing and Exporting Components

Every React component usually lives in its own file, and a React app is assembled by exporting components from the modules that define them and importing them wherever they are used. This is plain JavaScript ES module syntax (export and import) applied to components — there is nothing React-specific about the mechanism itself. What matters is the convention: default vs. named exports, one component per file, and how to organize imports as a folder grows from three files to thirty.

Overview: How Component Modules Work

A React component is just a JavaScript function that returns JSX. Once you write that function in its own file, it is invisible to the rest of your app until you export it. Any other file that wants to render it must import it first. The bundler (Vite, webpack, or whatever build tool the project uses) reads these import/export statements at build time to figure out the dependency graph — which files depend on which — and includes only what is actually reachable from your entry point (usually main.jsx, which renders <App />).

JavaScript modules support two kinds of exports, and React components use both:

  • Default export — at most one per file. It represents “the main thing this file provides.” It is imported without curly braces, and the importer is free to name it anything.
  • Named export — a file can have any number of these. Each one is imported with curly braces and must use the exact exported name, unless you rename it with as.

Most component files export exactly one component as the default export, because a file named Button.jsx conceptually provides “the Button component.” Named exports are typically reserved for files that provide several small, related things at once — a group of icon components, a set of constants, or a handful of tightly related sub-components that don’t deserve their own files.

Nothing about import/export changes how React renders. Splitting a component into its own file has zero effect on reconciliation, state, or re-renders — it is purely an organizational tool for you and your bundler. A component imported from another file behaves exactly like one defined in the same file; React only cares about the function reference it eventually calls, not which module it came from.

Syntax

The two export forms and their matching import forms:

// Default export — at most one per file
export default function Button() { /* ... */ }

// import (any name is fine, no braces)
import Button from "./Button";
import MyButton from "./Button"; // also valid, same component


// Named exports — any number per file
export function HomeIcon() { /* ... */ }
export function SettingsIcon() { /* ... */ }

// import (braces, exact names)
import { HomeIcon, SettingsIcon } from "./icons";

// renaming a named import
import { HomeIcon as Home } from "./icons";
Form Export syntax Import syntax How many per file
Default export default X; import AnyName from "./file"; One
Named export function X() {} import { X } from "./file"; Many
Named (renamed) import { X as Y } from "./file"; Many
Re-export (barrel) export { default as X } from "./X"; import { X } from "./components"; Many

Examples

Example 1: A default-exported component

// Button.jsx
function Button({ label, onClick }) {
  return (
    <button className="btn" onClick={onClick}>
      {label}
    </button>
  );
}

export default Button;
// App.jsx
import Button from "./Button";

function App() {
  return (
    <div>
      <Button label="Save" onClick={() => console.log("Saved!")} />
    </div>
  );
}

export default App;

This renders a single button reading “Save.” Clicking it logs Saved! to the console. Because Button is the default export of Button.jsx, App.jsx imports it with no curly braces and could call it anything it likes — the name Button here is just a convention that matches the file name, which keeps the code easy to follow.

Example 2: Named exports for a group of related components

// icons.jsx
export function HomeIcon() {
  return <span role="img" aria-label="home">🏠</span>;
}

export function SettingsIcon() {
  return <span role="img" aria-label="settings">⚙️</span>;
}
// Toolbar.jsx
import { HomeIcon, SettingsIcon } from "./icons";

function Toolbar() {
  return (
    <div className="toolbar">
      <HomeIcon />
      <SettingsIcon />
    </div>
  );
}

export default Toolbar;

This renders a toolbar containing a house icon and a gear icon side by side. Because icons.jsx has no default export, every import from it must use curly braces and match the exact function names. This pattern is common for small, closely related pieces that don’t warrant a file each — if you later add a SearchIcon, you just add another named export to the same file.

Example 3: A barrel file for a components folder

// Card.jsx
function Card({ title, children }) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <div className="card-body">{children}</div>
    </div>
  );
}

export default Card;
// components/index.js (the "barrel" file)
export { default as Card } from "./Card";
export { default as Toolbar } from "./Toolbar";
export { HomeIcon, SettingsIcon } from "./icons";
// App.jsx
import { Card, Toolbar, HomeIcon } from "./components";

function App() {
  return (
    <div>
      <Toolbar />
      <Card title="Welcome">
        <HomeIcon /> Hello there!
      </Card>
    </div>
  );
}

export default App;

This renders the toolbar from Example 2 above a card titled “Welcome” containing a home icon and the text “Hello there!” The index.js file inside the components folder is called a barrel file: it re-exports pieces from several other files under one path, so App.jsx can write a single import from "./components" instead of three separate imports from "./Card", "./Toolbar", and "./icons". Note that export { default as Card } from "./Card" takes Card’s default export and re-exports it as a named export called Card from the barrel file itself.

How It Works Step by Step

  1. The bundler starts at your entry file, sees import Button from "./Button" in App.jsx, and registers Button.jsx as a dependency of App.jsx.
  2. The bundler then parses Button.jsx, runs the module’s top-level code once, and records whatever value follows export default as that module’s default export.
  3. Back in App.jsx, the local identifier Button is bound to that exact function reference. Modules are cached: if ten different files import Button.jsx, the module runs once and every importer shares the same function reference.
  4. When React renders <Button label="Save" />, it calls the imported function exactly as it would call a function defined in the same file. React has no concept of “imported” vs. “local” components — it only sees a function to call and props to pass it.
  5. On a later re-render of App, React compares the previous and new element trees during reconciliation by checking each element’s type. Because the imported Button reference never changes between renders, React recognizes it as “the same component type” and updates it in place instead of unmounting and remounting it.
  6. During development, if you edit Button.jsx, the bundler’s fast-refresh feature reloads only that module (because the dependency graph is known statically from your import/export statements) and re-renders the components that used it, without losing the rest of the app’s state.

Common Mistakes

Mistake 1: Importing a default export with curly braces

// Button.jsx
export default function Button() {
  return <button>Click me</button>;
}

// App.jsx — WRONG
import { Button } from "./Button";

function App() {
  return <Button />;
}

This fails at runtime because Button.jsx has no named export called Button — only a default export. Importing it with { Button } gives you undefined, and rendering <undefined /> throws an error like “Element type is invalid.” Default exports are never imported with curly braces:

// App.jsx — CORRECT
import Button from "./Button";

function App() {
  return <Button />;
}

Mistake 2: Forgetting to export the component at all

// UserCard.jsx — WRONG, nothing is exported
function UserCard({ name }) {
  return <p>{name}</p>;
}
// App.jsx
import UserCard from "./UserCard"; // UserCard is undefined at runtime

It is easy to write a component, use it further down in the same file while prototyping, and forget to add the export keyword once you split it into its own file. Since UserCard.jsx exports nothing, the default import in App.jsx silently resolves to undefined rather than throwing at import time, and the error only surfaces when React tries to render it. The fix is to add export default:

// UserCard.jsx — CORRECT
function UserCard({ name }) {
  return <p>{name}</p>;
}

export default UserCard;

Best Practices

  • Give each component its own file, and name the file after the component in PascalCase (Button.jsx, not button.jsx) — on case-sensitive filesystems (Linux, most CI servers) an import with the wrong case fails even though it may work locally on macOS or Windows.
  • Use a default export for the one component a file is “about,” and named exports for small, closely related helpers that live alongside it.
  • Only add a barrel index.js for folders you treat as a public, package-like unit (like a shared components folder); adding one to every folder just adds a layer of indirection to trace through.
  • Avoid circular imports (file A imports from file B, which imports from file A) — if two components need to share logic, pull the shared piece into a third file that both import from instead.
  • Keep named export names identical to what you import them as, apart from deliberate as renames, so project-wide search and refactoring tools can find every usage.
  • Prefer shorter relative paths where possible, and configure a path alias (for example @/components/Button) in larger projects to avoid long ../../../../ chains.

Practice Exercises

  • Create a file Greeting.jsx that default-exports a component accepting a name prop and rendering “Hello, {name}!”. Import it into App.jsx and render it with a name of your choice.
  • Create a file shapes.jsx with three named exports — Circle, Square, and Triangle — each rendering a <div> with a distinct className. Import all three into App.jsx using a single named import statement and render them together.
  • Combine the components from the first two exercises behind a barrel file at components/index.js, then rewrite App.jsx to import everything from "./components" instead of individual files.

Summary

  • Components are just JavaScript functions, made visible to other files with export and brought in elsewhere with import.
  • A file can have one default export (imported without braces, any name) and any number of named exports (imported with braces, exact names).
  • Splitting components across files is purely organizational — it has no effect on rendering, state, or reconciliation.
  • A barrel file (index.js re-exporting from several files) lets a folder be imported from a single path, but adds indirection, so use it selectively.
  • The most common import/export bugs are mixing up default vs. named import syntax and forgetting the export keyword after moving a component into its own file.