Skip to content
aviral gupta

// B2.1 · ~30 min · Beginner

Your first component, import and export

After this lesson you define and nest components the way react.dev does, and split them into files with default and named exports.

Lesson 1 of 6 in B2 Describing the UI

Start of the module

You will be able to

  • Define a component with export default, a capitalised function and a return with parentheses for multi-line JSX
  • Nest components in a parent, and keep every definition at the top level
  • Split components into files, and import default and named exports the matching way
  1. Warm-up · Activity 1 of 7

    Warm-up from module B1: a component, a prop and curly braces. What text does Page show?

    function Greeting({ name }) {
      return <p>Hello, {name}</p>;
    }
    
    function Page() {
      return <Greeting name="Ada" />;
    }
  2. Predict · Activity 2 of 7

    Predict before you read on. The JSX starts on the line after return. What HTML does Gallery put into #root?

    function Profile() {
      return
        <img src="ada.jpg" alt="Ada" />;
    }
    
    function Gallery() {
      return <section><Profile /></section>;
    }
  3. Practice · Activity 3 of 7

    Fill in the keyword that makes Gallery the main export of its file.

    export ____ function Gallery() {
      return <h1>Gallery</h1>;
    }
  4. Practice · Activity 4 of 7

    Match each line in Button.jsx or main.jsx to what it means.

  5. Practice · Activity 5 of 7

    Gallery.jsx sits next to main.jsx. Which line in main.jsx gets the Profile component?

    // Gallery.jsx
    export default function Gallery() {
      return <section>…</section>;
    }
    
    export function Profile() {
      return <img src="ada.jpg" alt="Ada Lovelace" />;
    }
  6. Brain teaser · Activity 6 of 7

    Brain teaser. Counter is defined inside Page. You click the counter three times, then click Theme. What does the counter show now?

    function Page() {
      const [dark, setDark] = useState(false);
    
      function Counter() {
        const [count, setCount] = useState(0);
        return <button onClick={() => setCount(count + 1)}>{count}</button>;
      }
    
      return (
        <>
          <Counter />
          <button onClick={() => setDark(!dark)}>Theme</button>
        </>
      );
    }
  7. Apply · Activity 7 of 7

    Mini-task: a page for a book club, organised in components. In main.jsx, write Header (an <h1> with the club's name), Book (one <li> with a cover picture and a line of text) and Shelf (a <ul> that renders Book three times). The default export, App, renders Header and Shelf. Define every component at the top level. Run it and check that #root holds one heading and three list items. If you work in Vite, try moving Book into Book.jsx and importing it.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

A gallery split over three files

react.dev's gallery, split into files. Profile.jsx default-exports Profile, one picture. Gallery.jsx imports it and renders it three times; it default-exports Gallery and adds a named export, Heading. main.jsx imports all three: the default exports without braces, Heading in braces, each path with its extension. Its own default export, App, is what the runner renders into #root. The pictures do not load in the runner; the snapshot shows the HTML.

main.jsx

import Gallery from './Gallery.jsx';
import {Heading} from './Gallery.jsx';
import Profile from './Profile.jsx';

export default function App() {
  return (
    <>
      <Heading />
      <Gallery />
      <h2>Scientist of the month</h2>
      <Profile />
    </>
  );
}

Gallery.jsx

import Profile from './Profile.jsx';

export function Heading() {
  return <h1>Amazing scientists</h1>;
}

export default function Gallery() {
  return (
    <section>
      <Profile />
      <Profile />
      <Profile />
    </section>
  );
}

Profile.jsx

export default function Profile() {
  return (
    <img
      src="katherine-johnson.jpg"
      alt="Katherine Johnson"
    />
  );
}

Run it with

npm run dev

Output

<!-- #root -->
<div id="root"><h1>Amazing scientists</h1><section><img alt="Katherine Johnson" src="katherine-johnson.jpg"><img alt="Katherine Johnson" src="katherine-johnson.jpg"><img alt="Katherine Johnson" src="katherine-johnson.jpg"></section><h2>Scientist of the month</h2><img alt="Katherine Johnson" src="katherine-johnson.jpg"></div>
  • Profile.jsx is imported twice, by Gallery.jsx and by main.jsx; both get the same component.
  • Gallery.jsx has one default export, Gallery, and one named export, Heading. main.jsx imports them in two lines, as react.dev does.
  • No file name appears in the HTML: only what the components return. React wrote alt before src; the order of attributes makes no difference.
  • Locally in Vite, these files sit in src/ next to App.jsx.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads React and the compiler that turns JSX into JavaScript (up to 2 MB) and keeps them cached. Your components run in your browser and stay on your device.

Your page

Your page runs in an isolated frame. It has no access to this site, and its localStorage is kept in memory for this run only: the next run starts empty.

Run the program to see its page here.

Exercises

Exercise 1 of 2

Finish the split into files

TeamPage used to define everything in main.jsx. Title, Avatar and Footer have been moved into files: Avatar.jsx default-exports Avatar, and Labels.jsx has the named exports Title and Footer. Finish the split: delete the old copies from main.jsx, import the components from the files the matching way, and keep rendering Title, Avatar and Footer in <main>, in that order.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads React and the compiler that turns JSX into JavaScript (up to 2 MB) and keeps them cached. Your components run in your browser and stay on your device.

Your page

Your page runs in an isolated frame. It has no access to this site, and its localStorage is kept in memory for this run only: the next run starts empty.

Run the program to see its page here.

Hints
  1. Hint 1

    Open Avatar.jsx and Labels.jsx: is each component exported with default or without?

  2. Hint 2

    A default export is imported without braces: import Avatar from './Avatar.jsx';

  3. Hint 3

    Two named exports from one file go in one pair of braces: import {Title, Footer} from './Labels.jsx';

Show a solution

One way to solve it. Yours can look different and still pass the checks.

import Avatar from './Avatar.jsx';
import {Title, Footer} from './Labels.jsx';

export default function TeamPage() {
  return (
    <main>
      <Title />
      <Avatar />
      <Footer />
    </main>
  );
}
Run it on your computer

Install React 19.3 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.jsx

// The components now live in Avatar.jsx and Labels.jsx. Finish the split:
// delete these old copies and import the new ones instead.
function Title() {
  return <h1>Team</h1>;
}

function Avatar() {
  return <img className="avatar" src="ada.jpg" alt="Ada" />;
}

function Footer() {
  return <p>Made by us</p>;
}

export default function TeamPage() {
  return (
    <main>
      <Title />
      <Avatar />
      <Footer />
    </main>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import TeamPage from './main.jsx';

test('The heading comes from Title in Labels.jsx', () => {
  render(<TeamPage />);
  const heading = screen.getByRole('heading', {level: 1}).textContent;
  assert.equal(heading, 'Our team', 'the heading reads ' + JSON.stringify(heading));
});

test('The picture comes from Avatar in Avatar.jsx', () => {
  render(<TeamPage />);
  const alt = screen.getByRole('img').getAttribute('alt');
  assert.equal(alt, 'Ada Lovelace', 'the picture has alt=' + JSON.stringify(alt));
});

test('The footer comes from Footer in Labels.jsx', () => {
  render(<TeamPage />);
  assert.ok(screen.queryByText('Made with React'), 'no element shows the text Made with React');
});

test('Title, Avatar and Footer sit in <main> in this order', () => {
  render(<TeamPage />);
  const tags = [...screen.getByRole('main').children].map((child) => child.tagName.toLowerCase());
  assert.deepEqual(tags, ['h1', 'img', 'p'], '<main> holds ' + JSON.stringify(tags));
});

test('React prints no warnings', () => {
  const warnings = [];
  const original = console.error;
  console.error = (...args) => warnings.push(args.join(' '));
  try {
    render(<TeamPage />);
  } finally {
    console.error = original;
  }
  assert.deepEqual(warnings, [], 'console.error while rendering');
});

Avatar.jsx

export default function Avatar() {
  return <img className="avatar" src="ada-lovelace.jpg" alt="Ada Lovelace" />;
}

Labels.jsx

export function Title() {
  return <h1>Our team</h1>;
}

export function Footer() {
  return <p>Made with React</p>;
}

In a Vite React project (npm create vite@latest, template react), put main.jsx in src/ as App.jsx and start it with npm run dev. React renders it inside <StrictMode>, as here.

Run the program:

npm run dev

There is no command for the checks on your computer yet. They are in main.test.jsx, written with node:test and React Testing Library.

Exercise 2 of 2

Three photos with their own likes

Gallery shows one photo with a LikeButton, and LikeButton is defined inside Gallery. Click Like, then Dark mode: the likes are lost. Move LikeButton to the top level of the file, and show three photos (Sunrise, Forest and Harbour, each an <img> with that alt text), each with its own <LikeButton />. Keep the Dark mode button first.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads React and the compiler that turns JSX into JavaScript (up to 2 MB) and keeps them cached. Your components run in your browser and stay on your device.

Your page

Your page runs in an isolated frame. It has no access to this site, and its localStorage is kept in memory for this run only: the next run starts empty.

Run the program to see its page here.

Hints
  1. Hint 1

    Run the starter, click Like twice, then Dark mode, and watch the count.

  2. Hint 2

    Cut the whole function LikeButton() { … } and paste it above export default function Gallery().

  3. Hint 3

    Each photo is a <figure> with an <img> and a <LikeButton />; write three of them.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

import {useState} from 'react';

function LikeButton() {
  const [likes, setLikes] = useState(0);
  return <button onClick={() => setLikes(likes + 1)}>Like ({likes})</button>;
}

export default function Gallery() {
  const [dark, setDark] = useState(false);
  return (
    <section className={dark ? 'dark' : 'light'}>
      <button onClick={() => setDark(!dark)}>Dark mode</button>
      <figure>
        <img src="sunrise.jpg" alt="Sunrise" />
        <LikeButton />
      </figure>
      <figure>
        <img src="forest.jpg" alt="Forest" />
        <LikeButton />
      </figure>
      <figure>
        <img src="harbour.jpg" alt="Harbour" />
        <LikeButton />
      </figure>
    </section>
  );
}
Run it on your computer

Install React 19.3 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.jsx

import {useState} from 'react';

export default function Gallery() {
  const [dark, setDark] = useState(false);

  // TODO: move LikeButton out of Gallery, to the top level of the file.
  function LikeButton() {
    const [likes, setLikes] = useState(0);
    return <button onClick={() => setLikes(likes + 1)}>Like ({likes})</button>;
  }

  // TODO: show three photos, each with its own LikeButton.
  return (
    <section className={dark ? 'dark' : 'light'}>
      <button onClick={() => setDark(!dark)}>Dark mode</button>
      <figure>
        <img src="sunrise.jpg" alt="Sunrise" />
        <LikeButton />
      </figure>
    </section>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Gallery from './main.jsx';

const likes = () => screen.getAllByRole('button', {name: /^Like/}).map((button) => button.textContent);

test('Three photos, each with a like button', () => {
  render(<Gallery />);
  const photos = screen.getAllByRole('img').length;
  assert.equal(photos, 3, 'the gallery shows ' + photos + ' photo(s)');
  assert.deepEqual(likes(), ['Like (0)', 'Like (0)', 'Like (0)'], 'the like buttons read ' + JSON.stringify(likes()));
});

test('Each like button counts on its own', async () => {
  const user = userEvent.setup();
  render(<Gallery />);
  const first = screen.getAllByRole('button', {name: /^Like/})[0];
  await user.click(first);
  await user.click(first);
  assert.deepEqual(likes(), ['Like (2)', 'Like (0)', 'Like (0)'], 'after two clicks on the first: ' + JSON.stringify(likes()));
});

test('Likes survive a click on Dark mode', async () => {
  const user = userEvent.setup();
  render(<Gallery />);
  await user.click(screen.getAllByRole('button', {name: /^Like/})[0]);
  await user.click(screen.getByRole('button', {name: 'Dark mode'}));
  assert.equal(likes()[0], 'Like (1)', 'after one like and Dark mode, the first button reads ' + JSON.stringify(likes()[0]));
});

test('React prints no warnings', () => {
  const warnings = [];
  const original = console.error;
  console.error = (...args) => warnings.push(args.join(' '));
  try {
    render(<Gallery />);
  } finally {
    console.error = original;
  }
  assert.deepEqual(warnings, [], 'console.error while rendering');
});

In a Vite React project (npm create vite@latest, template react), put main.jsx in src/ as App.jsx and start it with npm run dev. React renders it inside <StrictMode>, as here.

Run the program:

npm run dev

There is no command for the checks on your computer yet. They are in main.test.jsx, written with node:test and React Testing Library.

Common mistakes

Importing a named export as a default

import Gallery from './Gallery.jsx';

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

What React prints

does not provide an export named 'default'

Why, and the fix

The browser's whole line reads SyntaxError: The requested module './Gallery.jsx' does not provide an export named 'default', and nothing runs. Gallery.jsx exports Gallery by name, so import it in braces: import {Gallery} from './Gallery.jsx'. Or make it the file's default with export default function Gallery(). The wording is V8's (Chrome, Node); Firefox and Safari word it differently.

Importing a default export in braces

import {Gallery} from './Gallery.jsx';

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

What React prints

does not provide an export named 'Gallery'

Why, and the fix

Braces ask for an export with that exact name, but Gallery.jsx has only a default export. The full line is a SyntaxError naming the module './Gallery.jsx'. Drop the braces: import Gallery from './Gallery.jsx'. react.dev: you will get an error if you try to import a default export the same way you would a named export. The wording is V8's; Firefox and Safari word it differently.

Two default exports in one file

import Gallery from './Gallery.jsx';

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

What React prints

SyntaxError: Duplicate export of 'default'

Why, and the fix

A file can have only one default export, so Gallery.jsx does not load, and nothing runs. Keep export default on the main component, Gallery, and make Profile a named export, export function Profile(), or drop its export if only Gallery uses it. The wording is V8's; Firefox and Safari word it differently.

React in the browser: React 19.3.0 (development build) and React Testing Library, MIT, served from this site; JSX is compiled by TypeScript 6.0.3, Apache-2.0. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

A component is a function that returns markup

react.dev builds a component in three steps. Export it: export default marks the main function of a file, and it is plain JavaScript, not React. Define it: function Profile() { }, with a capital letter. Add markup: return the JSX. A short return fits on one line. If the markup starts on the next line, wrap it in parentheses, return ( … ). Without them, JavaScript ends the statement after return: the component returns nothing and renders nothing, with no error to warn you.

Nest components, but define each at the top level

Once Profile exists, Gallery can render <Profile /> as often as it likes. Gallery is then the parent and each Profile a child. Many small components can share one file. But never define a component inside another one. react.dev says such code is very slow and causes bugs: on every render of Gallery, a new Profile function is created, React sees a different component in the same place, and resets its state. Define every component at the top level of the file, and pass data down with props.

Default and named exports

A file has at most one default export and any number of named ones. Import each the way it was exported: export default function Gallery() is imported as import Gallery from './Gallery.jsx', and you may choose any name. export function Profile() is imported as import {Profile} from './Gallery.jsx', and the name must match. Mixing the two forms is an error. In the course runner, files sit next to main.jsx and are imported with their extension; react.dev notes that './Gallery' without it also works with React.

Sources

Last reviewed October 5, 2026