Modules: import and export

Named and default exports, and how a component file is wired together.

What you'll learn

  • Named exports versus the default export
  • How import syntax mirrors the way a value was exported
  • Renaming imports, and why you would
  • The shape of a typical React component file

Why modules

A module is just a file. Everything you declare in it is private until you export it, and nothing from another file is available until you import it. That gives every file an explicit, readable contract — which is why a React codebase is a pile of small files rather than one large one.

The examples below have two files. Use the tabs above the editor to switch between them.

Named exports

Mark anything with export and it becomes part of the module's public surface. Import it by name, in braces:

import { add, PI } from "./math.js";

console.log(add(2, 3));
console.log(PI);

The braces are not an object — they are the import syntax for picking named exports out of a module.

Renaming and namespaces

Names collide. as renames an import, and * as pulls the whole module in under a single name:

import { add as sum } from "./math.js";
import * as math from "./math.js";

console.log(sum(2, 3));
console.log(math.add(10, 5), math.PI);

The default export

A module can have one default export — its single main thing. It is imported without braces, and the importing file chooses the name:

// No braces, and the name is ours to choose.
import sayHello from "./greeter.js";
import { version } from "./greeter.js";

console.log(sayHello("Ada"));
console.log("version", version);

Because the name is chosen by the importer, a default export is easy to import inconsistently across a codebase. Named exports keep one name everywhere, which is why many teams prefer them.

What this looks like in React

A component file is exactly this pattern. The component is the default export; helpers and types are named exports:

// Button.jsx
export const SIZES = ["small", "large"];

export default function Button({ children }) {
  return <button>{children}</button>;
}
// App.jsx
import Button, { SIZES } from "./Button.jsx";

You will also see this at the top of every React file you write:

import { useState, useEffect } from "react";

Same syntax — react is a module too, resolved from node_modules rather than a relative path. That is the only difference between "react" and "./math.js".

Imports are hoisted

All imports are resolved before any of the module body runs, regardless of where you write them. Convention is to put them at the top, because that is where readers look — not because the language requires it.

Common mistakes

  • Braces on a default import. import { greet } will not find a default export; drop the braces.
  • No braces on a named import. The mirror image of the same mistake.
  • Expecting a name to be importable. Nothing leaves a module without export.
  • More than one default per file. There can be only one.

Exercise

/index.js imports a multiply function that /math.js does not export yet. Export it, then check the console prints 12.

import * as math from "./math.js";

if (typeof math.multiply === "function") {
  console.log(math.multiply(3, 4));
} else {
  console.log("multiply is not exported yet — add export to it.");
}