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
- The bundler starts at your entry file, sees
import Button from "./Button"inApp.jsx, and registersButton.jsxas a dependency ofApp.jsx. - The bundler then parses
Button.jsx, runs the module’s top-level code once, and records whatever value followsexport defaultas that module’s default export. - Back in
App.jsx, the local identifierButtonis bound to that exact function reference. Modules are cached: if ten different files importButton.jsx, the module runs once and every importer shares the same function reference. - 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. - On a later re-render of
App, React compares the previous and new element trees during reconciliation by checking each element’stype. Because the importedButtonreference never changes between renders, React recognizes it as “the same component type” and updates it in place instead of unmounting and remounting it. - 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 yourimport/exportstatements) 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, notbutton.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.jsfor folders you treat as a public, package-like unit (like a sharedcomponentsfolder); 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
asrenames, 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.jsxthat default-exports a component accepting anameprop and rendering “Hello, {name}!”. Import it intoApp.jsxand render it with a name of your choice. - Create a file
shapes.jsxwith three named exports —Circle,Square, andTriangle— each rendering a<div>with a distinctclassName. Import all three intoApp.jsxusing 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 rewriteApp.jsxto import everything from"./components"instead of individual files.
Summary
- Components are just JavaScript functions, made visible to other files with
exportand brought in elsewhere withimport. - 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.jsre-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
exportkeyword after moving a component into its own file.
