Skip to the chapter
←ReactGGroundwork⚛React
React/Beginner/B3
3 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛B3 · Part 1 · Beginner

Components & composition

A component is a function that takes data and returns a description of a screen.

4 min read12 sections3 exercises
3 of 57

In this chapter

  • The whole idea
  • The capital letter is not style
  • Composition
  • children
  • Named slots

and 7 more

about 4 min
12On this page
  1. The whole idea
  2. The capital letter is not style
  3. Composition
  4. children
  5. Named slots
  6. Components must be pure
  7. Splitting one that grew
  8. The mistake that costs an afternoon
  9. The render tree is not the DOM tree
  10. Returning nothing
  11. One component per file, named after the file
  12. How big should a component be?

The whole idea

A React component is a JavaScript function that returns JSX. That is the entire definition. It takes one argument, conventionally called props, and it returns an element tree.

function Greeting() {
  return <h1>Hello</h1>;
}

// used like a tag
<Greeting />

The capital letter is not style

<greeting /> compiles to jsx("greeting", ...) — the string "greeting", which React reads as "an HTML tag called greeting". <Greeting /> compiles to jsx(Greeting, ...) — a reference to your function.

Lowercase means DOM tag; capitalised means component. That is why a lowercase component silently renders nothing instead of erroring.

Composition

Components nest, and that is how every React app is built: one tree, made of functions calling into other functions.

function App() {
  return (
    <Page>
      <Header />
      <Article title="Closures" />
      <Footer />
    </Page>
  );
}

children

Whatever you put between a component's tags arrives as props.children. This is the mechanism behind every wrapper you will ever write — cards, modals, layouts, page shells.

function Card({ title, children }) {
  return (
    <section className="card">
      <h2>{title}</h2>
      <div className="card__body">{children}</div>
    </section>
  );
}

<Card title="Notes">
  <p>Anything at all goes here.</p>
  <Button />
</Card>

Card knows nothing about what it wraps. That ignorance is the point: it is what makes it reusable. A component that reaches inside its children to check what they are has usually made a mistake.

Composition instead of configuration

When a component grows a pile of boolean props — showHeader, showFooter, compact, withIcon — that is usually a sign it should be taking children instead. Passing the header in is more flexible than passing a flag that asks it to draw one.

Named slots

children is one slot. When you need several, pass elements as ordinary props — there is nothing special about children except its name.

function Layout({ sidebar, main }) {
  return (
    <div className="layout">
      <aside>{sidebar}</aside>
      <main>{main}</main>
    </div>
  );
}

<Layout sidebar={<Nav />} main={<Article />} />

Components must be pure

Given the same props, a component must return the same thing and change nothing outside itself. React relies on this: it may call your function more than once for a single screen, and in development it deliberately does, to surface exactly this kind of bug.

Inside the component body
fineReading props and state, computing values, building JSX
noMutating a prop or a variable defined outside
noWriting to the DOM directly
noFetching, timers, subscriptions, localStorage

Everything in the "no" column belongs in an event handler or an effect. Purity is not dogma here — it is the assumption that lets React render, abandon that render, and start again without your code noticing.

Splitting one that grew

function Dashboard({ userId }) {
  // fetches, formats, and renders three unrelated panels
  const [user, setUser] = useState(null);
  const [invoices, setInvoices] = useState([]);
  const [alerts, setAlerts] = useState([]);
  ...180 lines...
}

The split follows the data, not the line count:

function Dashboard({ userId }) {
  return (
    <Grid>
      <ProfilePanel userId={userId} />
      <InvoicePanel userId={userId} />
      <AlertPanel userId={userId} />
    </Grid>
  );
}

Each panel now owns its own loading and error state, so a slow invoice request no longer blocks the profile from appearing. That is the real payoff of splitting — not tidiness, but independent failure and independent loading.

The mistake that costs an afternoon

Never define a component inside another component.

function Page() {
  // ✗ a brand-new function on every render
  function Row({ label }) {
    return <li>{label}</li>;
  }
  return <ul><Row label="one" /></ul>;
}

Each render creates a new Row function. React compares element types to decide whether to keep a DOM node, and a new function is a new type — so it throws the old node away and builds a fresh one every render. Anything inside loses its state, its focus and its scroll position. Move Row out to the module level and it is fixed.

The render tree is not the DOM tree

React builds a tree of your components; the browser holds a tree of DOM nodes. They are different shapes, and knowing that saves you when debugging.

// render tree               // DOM tree
App                          div.layout
 └ Layout                     ├ aside
    ├ Nav                     │  └ ul...
    └ Article                 └ main
       └ Markdown                └ article...

Layout, Nav and Article produce no DOM node of their own — they are functions that returned something. This is why React DevTools shows a tree the Elements panel does not, and why "wrapping in a component" costs nothing in the output.

Returning nothing

function Banner({ message }) {
  if (!message) return null;      // legal, renders nothing
  return <div className="banner">{message}</div>;
}

null, undefined and false are all valid returns. A component that renders conditionally does not need a wrapper around its call site — it can decide for itself.

One component per file, named after the file

Not a rule React enforces, but the convention every codebase converges on because it makes a file findable from a stack trace and from an import. Helpers used only by that component live in the same file, below it.

components/
  TaskList.jsx        // export function TaskList
  TaskList.module.css
  TaskRow.jsx

Give the function a name rather than exporting an anonymous arrow. React DevTools and error stacks both read that name, and export default () => ... shows up as Anonymous.

How big should a component be?

There is no line count that answers this. The useful test is whether it does one thing: if you cannot name it without saying "and", it is two components. A 200-line form component that is just a long form is fine. A 60-line component that fetches, formats and renders three unrelated panels is not.

Interview answer, one sentence

"A component is a pure function from props to elements. Composition — passing elements in as children or props — is how React avoids the configuration explosion you get when a component tries to know about every variation of itself."

03Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsA Card that composes with childrenbeginner
  • 8 testsForward the rest of the propsbeginner
  • 8 testsTwo views, one booleanbeginner
All 46 for this level →
B3

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. B2Previous · B2Thinking in React
  2. B3you are here
  3. Next · B4PropsData flows down. A component can read its props and must never write to them.4 min read

01On this page

12 sections · 0%

  1. 01The whole idea
  2. 02The capital letter is not style
  3. 03Composition
  4. 04children
  5. 05Named slots
  6. 06Components must be pure
  7. 07Splitting one that grew
  8. 08The mistake that costs an afternoon
  9. 09The render tree is not the DOM tree
  10. 10Returning nothing
  11. 11One component per file, named after the file
  12. 12How big should a component be?

[] chapters · t top