Building Your First Component Tree: App, Header, Main, WelcomeCard, Footer

Write and connect multiple components across separate files using imports and exports, and render a small, real component tree using JSX/TSX.

A React application is really just a tree of components rendering other components. In this lesson you'll build a small but complete tree — an App component composed of a Header, a Main section containing a WelcomeCard, and a Footer — spread across multiple files, exactly the pattern every real React project follows at a larger scale.

JSX and TSX recap: components as functions that return markup-like syntax

JSX (JavaScript XML) is a syntax extension that lets you write HTML-like markup directly inside JavaScript/TypeScript code; TSX is simply the name for JSX used inside a .tsx (TypeScript) file, so that the TypeScript compiler knows to expect and type-check JSX syntax in that file. Every component you write is an ordinary function; what makes it a React component is that it returns JSX describing what should appear on screen.

Because JSX is just JavaScript under the hood, you can use full TypeScript inside it — variables, conditionals, function calls — anywhere you're not literally inside a markup tag. This is what makes components composable: a component can call other components inside its returned JSX exactly the way it would call any other function, just written as a tag, like <WelcomeCard />.

Imports and exports: connecting files together

Each component typically lives in its own file, and files share code through ES module import/export statements. A component file usually has a single export default — the component itself — so that other files can import it with any local name they choose: import WelcomeCard from "./WelcomeCard";. Named exports (export function Header() {} imported as import { Header } from "./Header";) are used instead when a file exports more than one thing, such as a component plus a related type.

This file-per-component pattern (sometimes grouped into per-component folders as a project grows) keeps each piece of UI small, testable, and independently readable, while the import graph — which files import which — visibly represents your component tree's structure.

Composing the tree: App, Header, Main, WelcomeCard, Footer

App is the root component of the application (rendered by main.tsx). Its job is mostly composition: it imports and arranges its children in the order they should appear, rather than containing much markup or logic of its own. Here, App renders a Header, then a Main section, then a Footer, laid out top to bottom exactly as JSX describes them — JSX siblings render in the order they're written, just like HTML.

Main in turn renders a WelcomeCard as its child, one level deeper in the tree. Nesting like this — a component rendering a component that itself renders another component — is completely ordinary and is how real applications scale to hundreds of components: each one stays small and focused, and the tree grows deeper and wider as the app grows, not any single file.

Rendering the whole tree simply means: main.tsx renders <App />; React calls App, which returns JSX referencing <Header />, <Main />, and <Footer />; React then calls each of those in turn, recursively expanding the tree down to plain HTML elements (<header>, <div>, <footer>, and so on) at the leaves, which is what ultimately becomes real DOM nodes in the page.

A minimal component definition, export, and import

The import path "./WelcomeCard" omits the file extension — Vite's TypeScript/module resolution finds WelcomeCard.tsx automatically. A component's name must start with a capital letter (WelcomeCard, not welcomeCard) — JSX uses this casing rule to tell apart your components (<WelcomeCard />) from built-in HTML tags (<div>).

// Defining and exporting a component (WelcomeCard.tsx)
function WelcomeCard() {
  return (
    <div className="welcome-card">
      <h2>Welcome!</h2>
      <p>Glad you're here.</p>
    </div>
  );
}

export default WelcomeCard;

// Importing and using it elsewhere (Main.tsx)
import WelcomeCard from "./WelcomeCard";

function Main() {
  return (
    <main>
      <WelcomeCard />
    </main>
  );
}

export default Main;

Code Examples

Five small files, each exporting one component. App imports and composes Header, Main, and Footer as siblings; Main in turn imports and renders WelcomeCard as its child, producing the required App -> Header / Main -> WelcomeCard / Footer tree. main.tsx (unchanged from the Vite template) renders <App /> into the page, which cascades through this whole tree. Expected result: The rendered page shows, top to bottom: an 'SampleApp' heading, a welcome card with 'Welcome!' and its message, and a copyright footer line.

// Header.tsx
function Header() {
  return (
    <header>
      <h1>SampleApp</h1>
    </header>
  );
}
export default Header;

// WelcomeCard.tsx
function WelcomeCard() {
  return (
    <div className="welcome-card">
      <h2>Welcome!</h2>
      <p>Start learning React with TypeScript.</p>
    </div>
  );
}
export default WelcomeCard;

// Main.tsx
import WelcomeCard from "./WelcomeCard";

function Main() {
  return (
    <main>
      <WelcomeCard />
    </main>
  );
}
export default Main;

// Footer.tsx
function Footer() {
  return (
    <footer>
      <p>&copy; 2026 SampleApp</p>
    </footer>
  );
}
export default Footer;

// App.tsx
import Header from "./Header";
import Main from "./Main";
import Footer from "./Footer";

function App() {
  return (
    <>
      <Header />
      <Main />
      <Footer />
    </>
  );
}
export default App;

The component tree for this lesson

App is the root; Header, Main, and Footer are its direct children (siblings, rendered top to bottom); WelcomeCard is nested one level deeper, as Main's own child.

App
├── Header
├── Main
│   └── WelcomeCard
└── Footer

Important Rules

Component names must be capitalized (PascalCase) so JSX can distinguish <MyComponent /> (a component) from <div> (a built-in HTML element).

Each component file typically has one export default, imported under whatever local name the importing file chooses.

JSX can only return a single root element from a component — wrap multiple sibling elements in a parent element or a Fragment (<>...</>).

Composition (components rendering other components) is the primary way React applications are structured — prefer many small components over one large one.

Where this shows up

Every production React application — no matter how large — is this same pattern repeated hundreds or thousands of times: small, single-purpose components in their own files, composed together through imports into ever-larger trees, starting from one root App component. The App/Header/Main/Footer shape here is essentially the skeleton of nearly every real website layout you'll build in React.

function App() { return ( <Header /> <Main /> <Footer />); } // Error: JSX expressions must have one parent element function App() { return ( <> <Header /> <Main /> <Footer /> </>); }

A component's returned JSX must have exactly one root/top-level element. Listing multiple siblings directly is a syntax error; wrapping them in a Fragment (<>...</>) or a real element (like a <div>) fixes it without adding an unwanted wrapper node to the DOM.

[multiple-choice] In the App/Header/Main/WelcomeCard/Footer tree, what is WelcomeCard's relationship to App? A. WelcomeCard is App's direct child, same level as Header and Footer B. WelcomeCard is a grandchild of App, nested inside Main C. WelcomeCard and App are unrelated, siblings at the top level D. WelcomeCard renders App inside it

B. WelcomeCard is a grandchild of App, nested inside Main — WelcomeCard is rendered inside Main, and Main is rendered inside App — so WelcomeCard is nested two levels down from App, making it a grandchild, not a direct child.

[multiple-choice] Why must component function names start with a capital letter? A. It's just a style preference with no functional effect B. JSX uses the capitalization to distinguish a custom component (<MyComp />) from a built-in HTML tag (<div>) C. TypeScript requires all functions to be capitalized D. Lowercase component names cause a runtime crash on import

B. JSX uses the capitalization to distinguish a custom component (<MyComp />) from a built-in HTML tag (<div>) — JSX's compiler treats a lowercase tag name as a built-in HTML element string (like "div") and a capitalized tag name as a reference to a component variable — so an uncapitalized component wouldn't be recognized as a component at all.

[predict-output] If Main.tsx forgets to import WelcomeCard but still writes <WelcomeCard /> in its JSX, what happens? A. It silently renders nothing extra B. TypeScript/the bundler reports that WelcomeCard is not defined (an unresolved reference) C. React automatically finds and imports it D. It renders the text 'WelcomeCard' literally on the page

B. TypeScript/the bundler reports that WelcomeCard is not defined (an unresolved reference) — JSX component references are ordinary variable references under the hood — using <WelcomeCard /> without importing (or otherwise defining) WelcomeCard in that file is a 'cannot find name' error, caught by TypeScript/the build tool before the app even runs.

Add a Sidebar component (its own file, exporting a <aside> with some placeholder text) and render it inside Main, alongside WelcomeCard, so Main's JSX returns both as siblings.

// Sidebar.tsx function Sidebar() { return <aside>Related links</aside>; } export default Sidebar; // Main.tsx import WelcomeCard from "./WelcomeCard"; import Sidebar from "./Sidebar"; function Main() { return ( <main> <WelcomeCard /> <Sidebar /> </main> ); } export default Main;

Main's returned JSX needs a single root element wrapping both children — use a Fragment or a wrapping <div>.