Volume I index
Components Before Abstractions

Component Files, Imports, and Exports

Ishtmeet Singh @ishtms/July 14, 2026/9 min read
#react#components#modules#imports#exports

The first application can live in one App.jsx file. As named UI parts gain their own props, logic, and styles, the file stops showing the component tree at a useful level.

Moving LessonCard to another file does not turn it into a different kind of React component. ES modules make the existing function binding available to another module. React still receives the imported function as an element type.

The module connection uses ordinary JavaScript. One file exports a binding and another file imports it.

Begin with Two Components in One Module

App.jsx currently contains both functions.

jsx
function LessonCard() {
  return <article>Component Files</article>;
}

export default function App() {
  return <LessonCard />;
}

Both bindings exist in the same module scope. App can read LessonCard without an import.

The functions also have different responsibilities. App defines the page-level tree. LessonCard renders one lesson's markup. That named separation gives a reason for a file edge.

Do not create a file only because a function contains JSX. Keep a short helper component beside its only parent when the two change together and the parent stays readable.

Move the Component Without Changing It

Create src/LessonCard.jsx.

jsx
export default function LessonCard() {
  return <article>Component Files</article>;
}

The component body is unchanged. export default makes the function available as the module's one default export.

Import it in App.jsx.

jsx
import LessonCard from "./LessonCard.jsx";

Use the binding as before.

jsx
export default function App() {
  return <LessonCard />;
}

The relative path begins with ./ because the target file sits beside App.jsx. Vite resolves the module, evaluates it, and makes its default export available through the local binding LessonCard.

text
App.jsx imports ./LessonCard.jsx
  -> module supplies its default export
  -> local LessonCard binding refers to the function
  -> JSX uses that function as element type

A Default Export Has No Exported Name Requirement

A module can provide one default export. The importing module chooses its local binding name.

jsx
import Card from "./LessonCard.jsx";
jsx
<Card />

This still receives the default function exported by LessonCard.jsx.

The flexibility can also hide inconsistent naming. A component called LessonCard in one file, Card in another, and Item in a third becomes harder to search.

Use the component's source name for its default import unless the local context requires a clear distinction.

jsx
import LessonCard from "./LessonCard.jsx";

Only one value can occupy the default export position.

jsx
export default function LessonCard() {}
export default function ChapterCard() {}

The module is invalid because it declares two defaults.

Named Exports Keep Their Exported Names

A module can have several named exports.

jsx
export function ChapterHeading() {
  return <h1>Components Before Abstractions</h1>;
}

export function LessonProgress() {
  return <p>2 of 6 lessons complete</p>;
}

Import named exports inside braces.

jsx
import {
  ChapterHeading,
  LessonProgress,
} from "./chapter-ui.jsx";

The names must match the exported names. A missing named export produces a module error.

jsx
import { Progress } from "./chapter-ui.jsx";

The module exports LessonProgress, not Progress.

Alias a named import explicitly when another local binding needs that name.

jsx
import {
  LessonProgress as Progress,
} from "./chapter-ui.jsx";

Progress is now the local binding. The export remains named LessonProgress.

Choose Default or Named Exports

A default export works well when the file has one primary component and the filename identifies it.

text
LessonCard.jsx
  default export LessonCard

Named exports work well when one file exposes a small related set or when consistent imported names are useful.

text
chapter-ui.jsx
  named export ChapterHeading
  named export LessonProgress

Both styles work with React. Choose according to the module's public API; React renders either imported function the same way.

Use one style consistently within a feature instead of changing styles because a component gained another line. A mixed module is valid too.

jsx
export const lessonLimit = 6;

export default function LessonList() {
  // ...
}

The default import and named import then use separate syntax.

jsx
import LessonList, {
  lessonLimit,
} from "./LessonList.jsx";

For the first app, keep data constants in a data module once they have several consumers. Component modules should remain centered on their UI responsibility.

Relative Paths Start at the Importing File

Suppose the source becomes nested.

text
src/
  App.jsx
  components/
    LessonCard.jsx

App.jsx imports downward into the directory.

jsx
import LessonCard from "./components/LessonCard.jsx";

If LessonCard.jsx imports a data file back in src, it moves up one directory.

jsx
import { lessons } from "../lesson-data.js";

Each .. moves from the importing file's directory to its parent. The current terminal directory does not affect relative module paths inside source.

Keep the explicit extension in local imports. Vite can resolve selected omitted extensions, but explicit filenames make the edge clear and align with browser and ES module habits.

Filename Case Is Part of the Edge

This file is named LessonCard.jsx.

text
LessonCard.jsx

Write the same case in the import.

jsx
import LessonCard from "./LessonCard.jsx";

An import using ./lessoncard.jsx can appear to work on a case-insensitive file system and fail on a case-sensitive system. Git can also handle case-only renames differently across systems.

Treat stored casing as part of the module specifier. PascalCase filenames pair clearly with PascalCase component exports.

Modules Evaluate Before Their Importers Continue

Static imports are resolved and loaded before the importing module evaluates its body.

jsx
import LessonCard from "./LessonCard.jsx";

console.log("App module evaluated");

The exported binding must be established before App.jsx can use it. A syntax or top-level runtime error in LessonCard.jsx prevents the importer from completing.

Top-level code runs when the module is evaluated, not each time the component renders.

jsx
console.log("LessonCard module evaluated");

export default function LessonCard() {
  console.log("LessonCard rendered");
  return <article>One lesson</article>;
}

The first log runs during module evaluation. The second runs during component rendering. Remove both after observing the timing.

Do not start application work at module top level merely to make it run once.

jsx
localStorage.setItem("visited", "true");

Module evaluation can occur in tests, server builds, or other environments where browser storage is absent or where the write is unwanted. Move browser writes to the interaction or synchronization code that actually requires them.

Imports Are Live Bindings

ES module imports remain connected to the exporting module's binding. They are not writable local copies.

js
// lesson-data.js
export let currentLesson = 1;

export function advanceLesson() {
  currentLesson += 1;
}

An importer can read the updated binding after advanceLesson runs.

js
import {
  advanceLesson,
  currentLesson,
} from "./lesson-data.js";

The importer cannot assign to currentLesson directly.

For React application data, module variables are not a replacement for state. Changing an exported variable does not request a React render.

Avoid Circular Component Imports

A cycle exists when modules eventually import each other.

text
App.jsx imports LessonCard.jsx
LessonCard.jsx imports App.jsx

ES modules can represent cycles through live bindings, but evaluation order and uninitialized values become harder to reason about. Component cycles also signal that a child depends on its page-level parent.

Move shared data or a shared component to a third module.

text
App.jsx -> LessonCard.jsx
App.jsx -> lesson-data.js
LessonCard.jsx -> lesson-data.js

Both UI modules depend on the lower shared data module. Neither imports the other to reach that data.

Keep the Dependency Direction Readable

The first app can use this source layout.

text
src/
  App.jsx
  components/
    ChapterHeading.jsx
    LessonCard.jsx
    LessonList.jsx
  lesson-data.js
  main.jsx

main.jsx imports App. App imports page components. LessonList imports LessonCard. A component can import tightly coupled static data or receive data through props.

Avoid a generic components directory with hundreds of unrelated files. As the application grows, group related UI, data operations, and tests by feature. A flat layout is sufficient while the project has only a few components.

One File Does Not Mean One Component

A primary exported component can use a small private helper in the same file.

jsx
function LessonCount({ count }) {
  return <span>{count} lessons</span>;
}

export default function LessonList() {
  return <section>{/* list content */}</section>;
}

LessonCount does not need an export while no other module uses it. Keeping it private reduces the public module surface.

Move it after it gains another consumer or a separate responsibility that deserves an independent module. Create files around useful boundaries, not arbitrary line counts.

Repair Export and Import Mismatches

Read the source module before changing syntax.

This export is named.

jsx
export function LessonCard() {}

Its import needs braces.

jsx
import { LessonCard } from "./LessonCard.jsx";

This export is default.

jsx
export default function LessonCard() {}

Its import does not use braces.

jsx
import LessonCard from "./LessonCard.jsx";

Changing the import at random can replace one error with another. Match the import form to the actual export declaration.

Complete the File Split

Create ChapterHeading.jsx.

jsx
export default function ChapterHeading() {
  return <h1>Components Before Abstractions</h1>;
}

Create LessonProgress.jsx.

jsx
export default function LessonProgress() {
  return <p>2 of 6 lessons complete</p>;
}

Import both into App.jsx.

jsx
import ChapterHeading from "./components/ChapterHeading.jsx";
import LessonProgress from "./components/LessonProgress.jsx";

The rendered JSX remains focused on the page tree.

jsx
export default function App() {
  return (
    <main>
      <ChapterHeading />
      <LessonProgress />
    </main>
  );
}

Save and inspect the page. The visible DOM should remain the same even though the source module graph now has three component files.

Lesson Check

Explain each module edge in terms of JavaScript bindings.

text
export makes a module value available
import creates a local binding to that value
default import names are local choices
named imports must match or declare an alias
relative paths start at the importing file
component JSX uses the imported function as element type

Source Notes