React Interview Prep
React Core Concepts

Component Types & Composition

Building UIs Like LEGO, Not Cement

LinkedIn Hook

React gives you two ways to build components: class and functional.

One of them won. And it was not even close.

But here is the part most developers miss: knowing WHICH type to use is only half the interview question. The other half is knowing how to COMPOSE components together — and why React chose composition over inheritance.

Ever built a component that does 15 things? That is not a component. That is a monolith wearing a trench coat.

In this lesson, I break down functional vs class components, the children prop, compound components pattern, and the exact moment you should split a component — with code examples interviewers love to ask about.

Read the full lesson → [link]

#React #JavaScript #WebDevelopment #InterviewPrep #Frontend #ComponentDesign #ReactHooks


Component Types & Composition thumbnail


What You'll Learn

  • Why functional components replaced class components — and the one thing classes still do
  • How composition works in React and why React explicitly recommends it over inheritance
  • The children prop and how it enables flexible component design
  • The compound components pattern that libraries like Radix and Headless UI use
  • When a component is doing too much and needs to be split

The LEGO Analogy

Think of React components like LEGO bricks — not cement blocks.

With cement, you pour everything into one mold. You get one big, solid shape. Want to change one wall? You demolish the whole thing.

With LEGO, you build small, reusable pieces that snap together. Want to change one section? Pop out that brick and swap it. The rest stays intact.

React chose the LEGO approach. Every UI is a tree of small components composed together. A <Page> contains a <Header>, a <Sidebar>, and a <Content>. The <Header> contains a <Logo> and a <Nav>. Each piece is independent, reusable, and replaceable.

This is composition — and it is the single most important design principle in React.


Functional vs Class Components

Class Components — The Old Way

Before React 16.8 (February 2019), if you needed state or lifecycle methods, you had to use a class component:

import React, { Component } from "react";

class Greeting extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {
    document.title = `Clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `Clicked ${this.state.count} times`;
  }

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>Hello, {this.props.name}</p>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Click me</button>
      </div>
    );
  }
}

// Output: renders a greeting with a click counter
// Document title updates on every click

Problems with this:

  • this binding is confusing (this.state, this.props, this.setState, arrow functions vs .bind())
  • Logic is split across lifecycle methods (componentDidMount and componentDidUpdate often duplicate code)
  • Classes do not minify well
  • Hard to share stateful logic between components (led to HOCs and render props — complex patterns)

Functional Components — The Winner

The same component with hooks:

import { useState, useEffect } from "react";

function Greeting({ name }) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `Clicked ${count} times`;
  }, [count]);

  return (
    <div>
      <p>Hello, {name}</p>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

// Output: identical behavior — greeting with click counter
// But 60% less code and no `this` anywhere

Why Functional Won

AspectClassFunctional + Hooks
Statethis.state + this.setStateuseState
Side effectsSplit across 3+ lifecycle methodsOne useEffect
Logic reuseHOC / render props (complex)Custom hooks (simple)
this bindingYes, constant source of bugsNo this needed
Bundle sizeLarger (classes don't minify well)Smaller
Learning curveSteeperSimpler
React team focusNo new class features since 2019All new features are hook-based

The one thing classes still do that hooks cannot: Error Boundaries. As of React 19, there is still no hook equivalent for componentDidCatch and getDerivedStateFromError. You need a class component (or the react-error-boundary library which wraps one for you).

Component Types & Composition visual 1


Component Composition vs Inheritance

React's official documentation says it plainly: "We recommend using composition instead of inheritance."

In traditional OOP, you might extend a base class:

// DON'T DO THIS IN REACT
class Button extends Component { /* ... */ }
class DangerButton extends Button { /* ... */ }
class IconButton extends Button { /* ... */ }
class DangerIconButton extends DangerButton { /* wait, or IconButton? */ }
// Inheritance tree gets messy fast

In React, you compose instead:

// Composition — flexible and clear
function Button({ variant, icon, children }) {
  const className = variant === "danger" ? "btn-danger" : "btn-default";

  return (
    <button className={className}>
      {icon && <span className="btn-icon">{icon}</span>}
      {children}
    </button>
  );
}

// Usage — mix and match freely
<Button>Save</Button>
// Output: <button class="btn-default">Save</button>

<Button variant="danger">Delete</Button>
// Output: <button class="btn-danger">Delete</button>

<Button variant="danger" icon="🗑️">Delete</Button>
// Output: <button class="btn-danger"><span class="btn-icon">🗑️</span>Delete</button>

No inheritance hierarchy. One component, composed with props.


The children Prop

children is a special prop that contains whatever you put between a component's opening and closing tags. It is the backbone of composition in React.

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

// Usage
<Card title="User Profile">
  <img src="avatar.jpg" alt="avatar" />
  <p>Rakibul Hasan</p>
  <button>Follow</button>
</Card>

// Output:
// <div class="card">
//   <h2>User Profile</h2>
//   <div class="card-body">
//     <img src="avatar.jpg" alt="avatar" />
//     <p>Rakibul Hasan</p>
//     <button>Follow</button>
//   </div>
// </div>

The Card component does not know or care what its children are. It just renders them. This is specialization through composition — the Card provides the frame, the consumer fills the content.

Multiple Slots (Named Children via Props)

Sometimes you need more than one slot. Use regular props:

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

// Usage
<Layout sidebar={<Nav links={["Home", "About", "Contact"]} />}>
  <h1>Welcome to the site</h1>
  <p>Main content goes here</p>
</Layout>

// Output: sidebar on left with Nav, main content on right

This is React's answer to "slots" in Vue or "named yields" in Ember. No special API — just props.

Component Types & Composition visual 2


Compound Components Pattern

Compound components are a group of components that work together to form a complete UI, sharing implicit state. Think of <select> and <option> in HTML — they are useless alone but powerful together.

import { createContext, useContext, useState } from "react";

// Shared context for the compound component
const TabsContext = createContext();

function Tabs({ defaultTab, children }) {
  const [activeTab, setActiveTab] = useState(defaultTab);

  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabList({ children }) {
  return <div className="tab-list" role="tablist">{children}</div>;
}

function Tab({ value, children }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  const isActive = activeTab === value;

  return (
    <button
      role="tab"
      className={isActive ? "tab active" : "tab"}
      onClick={() => setActiveTab(value)}
    >
      {children}
    </button>
  );
}

function TabPanel({ value, children }) {
  const { activeTab } = useContext(TabsContext);
  if (activeTab !== value) return null;

  return <div role="tabpanel" className="tab-panel">{children}</div>;
}

// Attach sub-components to parent
Tabs.TabList = TabList;
Tabs.Tab = Tab;
Tabs.TabPanel = TabPanel;

// Usage — clean, readable, flexible
<Tabs defaultTab="profile">
  <Tabs.TabList>
    <Tabs.Tab value="profile">Profile</Tabs.Tab>
    <Tabs.Tab value="settings">Settings</Tabs.Tab>
    <Tabs.Tab value="billing">Billing</Tabs.Tab>
  </Tabs.TabList>

  <Tabs.TabPanel value="profile">
    <p>Your profile info here</p>
  </Tabs.TabPanel>
  <Tabs.TabPanel value="settings">
    <p>App settings here</p>
  </Tabs.TabPanel>
  <Tabs.TabPanel value="billing">
    <p>Billing details here</p>
  </Tabs.TabPanel>
</Tabs>

// Output: clicking a tab shows only that tab's panel

Why this pattern matters in interviews:

  • Libraries like Radix UI, Headless UI, and Reach UI all use it
  • It demonstrates understanding of Context, composition, and API design
  • It shows you can build flexible, reusable component APIs

When to Split a Component

A common interview question: "How do you know when a component is too big?"

Here are the signals:

Split When:

  1. It has multiple responsibilities — A component that fetches data, handles form validation, AND renders a complex UI should be at least 3 components
  2. You are passing too many props — If a component takes 10+ props, it is probably doing too much
  3. Part of it re-renders unnecessarily — If only the header changes but the whole page re-renders, extract the header
  4. You copy-paste JSX — Repeated UI patterns should become a component
  5. The file is over 200 lines — Not a hard rule, but a strong signal

Do NOT Split When:

  1. It would create prop drilling — Splitting just to have smaller files but passing 8 props through 3 levels defeats the purpose
  2. The pieces are never reused — Not every <div> needs to be its own component
  3. It makes the code harder to follow — If splitting forces readers to jump between 5 files to understand one feature, keep it together
// BEFORE — one component doing everything
function UserDashboard() {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [notifications, setNotifications] = useState([]);

  useEffect(() => { /* fetch user */ }, []);
  useEffect(() => { /* fetch posts */ }, []);
  useEffect(() => { /* fetch notifications */ }, []);

  return (
    <div>
      {/* 50 lines of header JSX */}
      {/* 80 lines of post list JSX */}
      {/* 40 lines of notification panel JSX */}
    </div>
  );
}

// AFTER — composed from focused components
function UserDashboard() {
  return (
    <div>
      <UserHeader />
      <PostList />
      <NotificationPanel />
    </div>
  );
}

// Each child component owns its own data fetching and rendering
// Easier to test, easier to maintain, easier to explain in an interview

Common Mistakes

  • Using inheritance to extend components. React components should never extend other components (except React.Component or React.PureComponent for class components). Use composition with props and children instead. If you mention inheritance in a React interview, it is a red flag.

  • Ignoring the children prop. Many developers pass content as a regular prop (content={<Something />}) when children would make the API much cleaner. If something goes between opening and closing tags, use children.

  • Over-splitting components too early. Extracting every 5 lines into its own component creates a maze of tiny files with excessive prop passing. Split when you have a reason (reuse, readability, performance), not because "smaller is always better."


Interview Questions

Q: What are the differences between functional and class components?

Q: Can functional components completely replace class components?

Almost. The only feature that still requires a class component is Error Boundaries (componentDidCatch / getDerivedStateFromError). For everything else, functional components with hooks are the standard.

Q: Why does React recommend composition over inheritance?

Q: What is the compound components pattern and when would you use it?

Compound components are a set of components that share implicit state through Context and work together as a unit (like <Tabs> + <Tab> + <TabPanel>). Use it when building reusable component libraries where the consumer needs flexible control over structure and layout while the internal state logic is managed automatically.

Q: How do you decide when to split a component into smaller ones?


Quick Reference — Cheat Sheet

COMPONENT TYPES
======================================================
Class Component          | Functional Component
-------------------------|----------------------------
class X extends Component| function X(props) { }
this.state / setState    | useState()
lifecycle methods        | useEffect()
this.props               | props (argument)
Still needed for:        | Everything else
  Error Boundaries       |
======================================================

COMPOSITION PATTERNS
======================================================
children prop       → Content between <Parent>...</Parent>
Named slots         → Pass JSX as regular props
Compound components → Related components + shared Context
======================================================

WHEN TO SPLIT
======================================================
YES: Multiple responsibilities, 10+ props, copy-paste JSX
NO:  Creates prop drilling, never reused, harder to read
======================================================

Previous: Lesson 1.3 — JSX — Not HTML → Next: Lesson 2.1 — Props — Passing Data Down →


This is Lesson 1.4 of the React Interview Prep Course — 10 chapters, 42 lessons.

On this page