Topic #13Foundational15 min read

Components

Components from first principles: function vs class components, purity and idempotent rendering, composition over inheritance, the children prop and slot patterns, container/presentational split, higher-order and render-prop patterns, and the rules that keep components predictable.

#react#components#composition#children#purity#hoc#render-props#function-components

What a component is. A component is a reusable, self-contained piece of UI. In modern React it is simply a JavaScript function that accepts a single props object and returns markup (JSX). You build an entire screen by composing components — small focused ones (a button, an avatar) nested inside larger ones (a card, a page). This nesting is the app.

Function vs class components. React originally used class components (extending React.Component, with render() and lifecycle methods like componentDidMount). Modern React uses function components plus Hooks, which are simpler, less verbose, and now the default in the docs. You'll still read class components in older codebases, but write function components going forward.

Capitalization is load-bearing. Component names must start with a capital letter. JSX uses the first letter to decide: a lowercase tag (<button>) is treated as a built-in DOM element (a string type), while a capitalized tag (<Button>) is resolved to a variable in scope — your component. Lowercasing a component makes React look for a nonexistent HTML tag and render nothing useful.

Components must be pure (during render). A component's render logic should be a pure function of its props and state: given the same inputs it returns the same output, and it must not mutate anything outside its scope or perform side effects (no fetching, no DOM writes, no subscriptions) while rendering. Purity is what lets React call your component multiple times, skip it, or run it in the background safely. Side effects belong in event handlers or useEffect.

Composition over inheritance. React's reuse strategy is composition, not class inheritance. You don't extend a BaseCard to make a WarningCard; instead you build a generic Card and pass it different content and props. Combining small components covers every case inheritance would, without fragile hierarchies — this is an explicit recommendation in the React docs.

The children prop — the primary composition tool. Whatever you place between a component's opening and closing tags arrives as props.children. This lets you build generic containers (a Card, a Modal, a Layout) that wrap arbitrary content without knowing what it is. It's React's version of a 'slot': the container owns the frame, the caller owns the contents.

Multiple slots via props. When you need more than one insertion point, pass JSX through named props instead of children: <Page header={<Nav />} sidebar={<Menu />}>{main}</Page>. Because JSX is just a value, any prop can hold an element tree — this is how design-system layout components expose several regions.

Container vs presentational split. A useful convention: presentational components are pure and take everything via props (they just render), while container components hold state, fetch data, and pass results down. This separation makes the presentational pieces trivially reusable and testable. Hooks have blurred the hard line, but the mental split — 'who owns data' vs 'who draws pixels' — is still valuable.

Advanced reuse patterns. Before Hooks, cross-cutting logic was shared via higher-order components (a function that takes a component and returns an enhanced one, withAuth(Profile)) and render props (a component that calls a function child to share state, <Mouse>{pos => ...}</Mouse>). You should recognize both — they appear in libraries — but custom Hooks are now the idiomatic way to share stateful logic.

Keep components small and single-responsibility. A component that fetches, transforms, lays out, and handles five events is a refactor waiting to happen. Split by responsibility; a good heuristic is that you should be able to name the component after the one thing it does. Small components compose better, re-render more narrowly, and are easier to test.

Gotchas. Never define a component inside another component's body — it gets a brand-new identity on every render, so React unmounts and remounts it, discarding its state and DOM. Don't call your rendering function like a plain function (Component()); render it as an element (<Component />) so React manages its lifecycle. And a component must return something renderable (JSX, a string/number, an array, or null) — returning undefined is an error.

The mental model (memorise this). A component is a pure function props -> UI. You compose small components into big ones, pass content through children (and extra slots through named props), keep side effects out of render, and reuse logic with custom Hooks rather than inheritance. Capitalize the name, render it as <Component />, and never declare it inside another component.

Backend Analogy

A React component is like a stateless request handler or a pure service method: it takes an input object (props) and returns a description of output, without secretly mutating global state. Composition over inheritance is the same guidance you follow on the backend — prefer small collaborating services and dependency injection over deep class hierarchies (favor composition, à la 'prefer composition to inheritance' from Effective Java). The children prop is a template-method / slot: the container defines the skeleton and the caller supplies the body, much like passing a lambda into a higher-order method. Container-vs-presentational mirrors the controller/service split (orchestration and data) versus the view (pure rendering). Custom Hooks replacing HOCs/render props is like extracting shared behavior into a reusable helper function instead of wrapping everything in decorators.

Key Insights
  • A modern component is a JavaScript function that takes props and returns JSX; you compose small ones into larger ones.
  • Function components + Hooks are today's default; class components with lifecycle methods are legacy but still readable.
  • Component names must be capitalized so JSX treats them as components, not built-in DOM tags.
  • Render logic must be pure — same inputs give same output, with no side effects during rendering.
  • React reuses via composition, not inheritance: build generic components and pass different props/content.
  • props.children carries whatever sits between a component's tags — the primary slot for generic containers.
  • For multiple insertion points, pass JSX through named props (header, sidebar) since JSX is just a value.
  • Container components own state/data; presentational components are pure and render from props.
  • HOCs and render props are legacy logic-sharing patterns; custom Hooks are the modern replacement.
  • Never define a component inside another component's render — it remounts every render and loses state.

Worked Code

Function component vs legacy class component
TSX
// MODERN: function component (write these)
function Greeting({ name }: { name: string }) {
  return <h1>Hello, {name}</h1>;
}

// LEGACY: class component (you'll still read these in old code)
import React from 'react';
class GreetingClass extends React.Component<{ name: string }> {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
// Same output; the function version has no 'this', less boilerplate,
// and uses Hooks instead of lifecycle methods for state/effects.
Composition with the children prop (a generic container)
TSX
// A generic, reusable frame that doesn't care what's inside it.
function Card({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <section className="rounded border p-4 shadow-sm">
      <h3 className="font-semibold">{title}</h3>
      <div className="mt-2">{children}</div> {/* the slot */}
    </section>
  );
}

// Callers supply arbitrary content between the tags:
function Dashboard() {
  return (
    <Card title="Expenses">
      <p>Total this month: ₹12,400</p>   {/* becomes props.children */}
      <button>View report</button>
    </Card>
  );
}
Multiple slots via named props (JSX is just a value)
TSX
type PageProps = {
  header: React.ReactNode;
  sidebar: React.ReactNode;
  children: React.ReactNode; // main content
};

// A layout component with three insertion points.
function Page({ header, sidebar, children }: PageProps) {
  return (
    <div className="grid grid-cols-[240px_1fr]">
      <header className="col-span-2">{header}</header>
      <aside>{sidebar}</aside>
      <main>{children}</main>
    </div>
  );
}

// Usage: pass element trees through props.
// <Page header={<Nav />} sidebar={<Menu />}>{<Report />}</Page>
Container vs presentational split
TSX
// PRESENTATIONAL: pure, gets everything via props, trivially testable.
function ExpenseList({ items }: { items: { id: number; label: string }[] }) {
  return (
    <ul>
      {items.map(i => <li key={i.id}>{i.label}</li>)}
    </ul>
  );
}

// CONTAINER: owns state/data, then delegates rendering downward.
import { useState, useEffect } from 'react';
function ExpenseListContainer() {
  const [items, setItems] = useState<{ id: number; label: string }[]>([]);
  useEffect(() => {
    // side effects live here, NOT in render
    fetch('/api/expenses').then(r => r.json()).then(setItems);
  }, []);
  return <ExpenseList items={items} />; // pass data down
}
The classic gotcha: never declare a component inside render
TSX
// ❌ BAD: Row is redefined every render -> new identity ->
//    React remounts it each time, losing its state and DOM.
function TableBad({ rows }: { rows: string[] }) {
  function Row({ text }: { text: string }) { return <li>{text}</li>; }
  return <ul>{rows.map((t, i) => <Row key={i} text={t} />)}</ul>;
}

// ✅ GOOD: define components at module scope so their identity is stable.
function Row({ text }: { text: string }) { return <li>{text}</li>; }
function TableGood({ rows }: { rows: string[] }) {
  return <ul>{rows.map(t => <Row key={t} text={t} />)}</ul>;
}

Interview-Ready Q&A

A component is a reusable, self-contained piece of UI. In modern React it is a function that takes a props object and returns JSX describing what to render. You build an app by composing components — nesting small, focused ones inside larger ones — so the whole UI is a tree of components rendering other components.

Things to Remember
  • 1A component is a function that takes props and returns JSX; compose small ones into big ones.
  • 2Write function components with Hooks; recognize legacy class components with lifecycle methods.
  • 3Capitalize component names so JSX treats them as components, not DOM tags.
  • 4Keep render pure: no side effects, same inputs -> same output.
  • 5Reuse via composition (children, named-prop slots), not inheritance.
  • 6children is the primary slot; use named props for multiple insertion points.
  • 7Split container (owns data/state) from presentational (pure, renders props).
  • 8Prefer custom Hooks over HOCs and render props for sharing logic.
  • 9Never declare a component inside another component's body.
  • 10Render as <Component />, not Component(), and always return something renderable (or null).

References & Further Reading