Splitting into files

Give each component its own file, and wire the app back together with export and import.

The watchlist is one file, and that file is long: a card, four stills, the data and the page. You split a file when there is something worth splitting, and now there is.

One file per thing

A component is a named piece of the page. Once the pieces have names, each one can live in its own file.

Press Explode to pull this blog card apart, then switch on the second card.

On the page
A living room with a grey sofa, rattan pendant lights and plants
Thumbnail
InteriorBadge

Top 5 Living Room Inspirations

Heading

Curated vibrants colors for your living, make it pop & calm in the same time.

Text
Read moreReadMoreLink
Card
BlogCard
function BlogCard() {  return (    <Card>      <Thumbnail        src="/img/blog-card/living-room.jpg"        alt="A living room with a grey sofa, rattan pendant lights and plants"        objectPosition="50% 50%"      />      <Badge>Interior</Badge>      <Heading>Top 5 Living Room Inspirations</Heading>      <Text>        Curated vibrants colors for your living, make it pop        & calm in the same time.      </Text>      <ReadMoreLink href="/blog/living-room">        Read more      </ReadMoreLink>    </Card>  );}

Photo by Spacejoy on Unsplash.

Six small components, and the second card is the same six with different props. In a real project each of those six sits in its own file.

Handing a component out

Nothing leaves a file until you export it. export default marks the one main thing a file gives out, and import brings it in. This is the same import and export as Modules: import and export; here they carry components.

ShowCard has its own file below. Use the tabs above the editor to switch between the files.

// "./ShowCard" is the file sitting next to this one.
import "./styles.css";
import ShowCard from "./ShowCard";

export default function App() {
  return (
    <div className="page">
      <ShowCard title="The Bear" year={2022} still="still-warm" />
      <ShowCard title="Severance" year={2022} still="still-cold" />
    </div>
  );
}

Two cards, from two files. The path is relative: ./ means "next to this file", and the .js is left off because the tools add it for you.

A default export arrives without a name of its own, so the importing file chooses one. Choose the component's own name, and keep the capital letter, or React reads <showCard /> as an HTML tag.

Named exports for the rest

A file can have one default export and as many named exports as you like. Use the default for the file's main thing, and named exports for the smaller things beside it.

export const shows = [...];      // import { shows } from "./shows";
export default function ShowCard() {...}  // import ShowCard from "./ShowCard";

A component file gets a default: there is one main thing in it, and every file that imports it can call it what it likes. A file of data or styles gets named exports: there is no single main thing, and the name should be the same everywhere.

Anything with no export in front of it stays private to its file.

The component tree

The watchlist is App, which renders the cards. Pick a layer to see its code, then switch on As files to give each layer a file of its own.

On the page

My watchlist

T
Drama
The Bear
2022·8.6
ShowCard
S
Thriller
Severance
2022·8.7
ShowCard
A
Sci-Fi
Andor
2022·8.4
ShowCard
ShowList
App
App
// `page` is a plain style object, left out here.function App() {  return (    <div style={page}>      <h1 style={{ margin: "0 0 20px", fontSize: 20 }}>My watchlist</h1>      <ShowList />    </div>  );}
ShowList
const shows = [  { title: "The Bear", year: 2022, genre: "Drama", rating: 8.6 },  { title: "Severance", year: 2022, genre: "Thriller", rating: 8.7 },  { title: "Andor", year: 2022, genre: "Sci-Fi", rating: 8.4 },]; function ShowList() {  return (    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>      {shows.map((show) => (        <ShowCard key={show.title} show={show} />      ))}    </div>  );}
ShowCard
// The style objects are left out; still() makes the artwork.function ShowCard({ show }) {  return (    <div style={card}>      <div style={still(show.title)}>        <span style={genre}>{show.genre}</span>      </div>      <div style={body}>        <span style={title}>{show.title}</span>        <span style={meta}>          {show.year} · ★ {show.rating}        </span>        <button style={action}>+ Watchlist</button>      </div>    </div>  );}

Each file exports one component, and the file above it imports that component by path. The lines drawn between the panels are those imports.

The render tree and the import graph are not the same tree. A component that renders another is one; a file that imports anything at all, data or styles included, is the other.

The watchlist in four files

Here is the app you ended the last lesson with, in four files instead of one. Nothing about the page changes.

  • /shows.js — the data, as a named export. The still classes stay private.
  • /pill.js — the shared pill class name, as a named export.
  • /ShowCard.js — the card, as the default export.
  • /App.js — imports both, and still does the filtering and the mapping.
// A default import, then a named one. The braces are the difference.
import "./styles.css";
import ShowCard from "./ShowCard";
import { shows } from "./shows";

export default function App() {
  const worthWatching = shows.filter((show) => show.rating >= 8);

  return (
    <div className="page">
      <p className="lede">
        {worthWatching.length > 0
          ? worthWatching.length + " of " + shows.length + " shows are worth watching."
          : "Nothing worth watching yet."}
      </p>
      <div className="row">
        {worthWatching.map((show) => (
          <ShowCard key={show.id} show={show} />
        ))}
      </div>
    </div>
  );
}

The same three cards as before. App renders ShowCard, so those two are the render tree; pill.js and shows.js draw nothing at all, and are only in the import graph.

Common mistakes

  • Braces on a default import. import { ShowCard } from "./ShowCard" looks for a named export called ShowCard, finds nothing, and React stops with an invalid element type.
  • A path with no ./. from "shows" looks for an installed package, not for the file next to yours.
  • A name with no export. It stays private, so the import brings back undefined.

Exercise

The four files are the same as above, with one import changed. The page still loads, but the genre and Top rated text now sit bare on the artwork: no rounded background, no padding.

Fix /ShowCard.js so both pills come back, and the page reads:

3 of 4 shows are worth watching.

with three cards, and on each one the genre in a rounded pill on the artwork, plus a second Top rated pill beside it on The Bear and Severance.

Hint: import * as styles brings in the module's named exports. Read how pill leaves /pill.js.

import "./styles.css";
import ShowCard from "./ShowCard";
import { shows } from "./shows";

export default function App() {
  const worthWatching = shows.filter((show) => show.rating >= 8);

  return (
    <div className="page">
      <p className="lede">
        {worthWatching.length > 0
          ? worthWatching.length + " of " + shows.length + " shows are worth watching."
          : "Nothing worth watching yet."}
      </p>
      <div className="row">
        {worthWatching.map((show) => (
          <ShowCard key={show.id} show={show} />
        ))}
      </div>
    </div>
  );
}