Component Files, Imports, and 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.
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.
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.
import LessonCard from "./LessonCard.jsx";Use the binding as before.
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.
App.jsx imports ./LessonCard.jsx
-> module supplies its default export
-> local LessonCard binding refers to the function
-> JSX uses that function as element typeAn import creates a local binding to an exported module value. React does not search the project directory for component names.
A Default Export Has No Exported Name Requirement
A module can provide one default export. The importing module chooses its local binding name.
import Card from "./LessonCard.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.
import LessonCard from "./LessonCard.jsx";Only one value can occupy the default export position.
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.
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.
import {
ChapterHeading,
LessonProgress,
} from "./chapter-ui.jsx";The names must match the exported names. A missing named export produces a module error.
import { Progress } from "./chapter-ui.jsx";The module exports LessonProgress, not Progress.
Alias a named import explicitly when another local binding needs that name.
import {
LessonProgress as Progress,
} from "./chapter-ui.jsx";Progress is now the local binding. The export remains named LessonProgress.
Braces in an import select named exports. They do not create a JavaScript object and they are unrelated to JSX expression braces.
Choose Default or Named Exports
A default export works well when the file has one primary component and the filename identifies it.
LessonCard.jsx
default export LessonCardNamed exports work well when one file exposes a small related set or when consistent imported names are useful.
chapter-ui.jsx
named export ChapterHeading
named export LessonProgressBoth 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.
export const lessonLimit = 6;
export default function LessonList() {
// ...
}The default import and named import then use separate syntax.
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.
src/
App.jsx
components/
LessonCard.jsxApp.jsx imports downward into the directory.
import LessonCard from "./components/LessonCard.jsx";If LessonCard.jsx imports a data file back in src, it moves up one directory.
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.
LessonCard.jsxWrite the same case in the import.
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.
Match every directory and filename character exactly. A local success on a case-insensitive system does not repair the source path.
Modules Evaluate Before Their Importers Continue
Static imports are resolved and loaded before the importing module evaluates its body.
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.
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.
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.
// lesson-data.js
export let currentLesson = 1;
export function advanceLesson() {
currentLesson += 1;
}An importer can read the updated binding after advanceLesson runs.
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.
App.jsx imports LessonCard.jsx
LessonCard.jsx imports App.jsxES 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.
App.jsx -> LessonCard.jsx
App.jsx -> lesson-data.js
LessonCard.jsx -> lesson-data.jsBoth UI modules depend on the lower shared data module. Neither imports the other to reach that data.
Do not solve a circular import by moving everything into one global object. Identify the shared value and place it in a focused module.
Keep the Dependency Direction Readable
The first app can use this source layout.
src/
App.jsx
components/
ChapterHeading.jsx
LessonCard.jsx
LessonList.jsx
lesson-data.js
main.jsxmain.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.
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.
export function LessonCard() {}Its import needs braces.
import { LessonCard } from "./LessonCard.jsx";This export is default.
export default function LessonCard() {}Its import does not use braces.
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.
export default function ChapterHeading() {
return <h1>Components Before Abstractions</h1>;
}Create LessonProgress.jsx.
export default function LessonProgress() {
return <p>2 of 6 lessons complete</p>;
}Import both into App.jsx.
import ChapterHeading from "./components/ChapterHeading.jsx";
import LessonProgress from "./components/LessonProgress.jsx";The rendered JSX remains focused on the page tree.
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.
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 typeSource Notes
- MDN, JavaScript modules
- MDN,
export - MDN,
import - React documentation, Importing and Exporting Components