We use cookies to enhance your experience on the site
CodeWorlds

Composition vs Inheritance -- Building ships from modules

Welcome back to the Space Station, pilot! In this module, we'll explore advanced React design patterns that will help you build scalable and flexible applications. Let's start with the most important principle -- composition.

Why does React prefer composition?

Imagine you're building a spaceship. You have two approaches:

  • Inheritance -- you design one base ship, then create increasingly specialized versions. Each new version inherits everything from the previous one, even what it doesn't need. You quickly end up with an unreadable class hierarchy.
  • Composition -- you build independent modules (engine, cockpit, shields, sensors) and assemble them in any configuration. Each module does one thing well.

React intentionally does not support component inheritance. Instead, it gives us powerful composition tools.

Specialization through props

Instead of creating base classes and subclasses, in React we create a general component and specialize it through props:

1// General button component
2function SpaceButton({ variant, size, children, onClick }) {
3  const baseStyle = "space-btn";
4  const variantStyle = variant === "danger" ? "btn-danger" : "btn-primary";
5  const sizeStyle = size === "large" ? "btn-lg" : "btn-sm";
6
7  return (
8    <button className={`${baseStyle} ${variantStyle} ${sizeStyle}`} onClick={onClick}>
9      {children}
10    </button>
11  );
12}
13
14// Specialization through props -- NOT through inheritance
15function LaunchButton({ onClick }) {
16  return (
17    <SpaceButton variant="danger" size="large" onClick={onClick}>
18      LAUNCH
19    </SpaceButton>
20  );
21}

Containment -- slots and children

React allows components to not know in advance what their children will be. We use

children
to create containers:

1// Container component (doesn't know what will be inside)
2function SpacePanel({ title, children }) {
3  return (
4    <div className="space-panel">
5      <div className="panel-header">
6        <h2>{title}</h2>
7      </div>
8      <div className="panel-body">
9        {children}
10      </div>
11    </div>
12  );
13}
14
15// Usage -- insert any content
16function Dashboard() {
17  return (
18    <SpacePanel title="Mission Status">
19      <p>Fuel: 87%</p>
20      <p>Speed: 12,000 km/s</p>
21    </SpacePanel>
22  );
23}

Multiple slots

When one

children
isn't enough, we can use multiple props as slots:

1function SpaceLayout({ header, sidebar, children }) {
2  return (
3    <div className="layout">
4      <header className="layout-header">{header}</header>
5      <aside className="layout-sidebar">{sidebar}</aside>
6      <main className="layout-main">{children}</main>
7    </div>
8  );
9}
10
11function MissionControl() {
12  return (
13    <SpaceLayout
14      header={<Navigation />}
15      sidebar={<CrewList />}
16    >
17      <MissionMap />
18      <StatusPanel />
19    </SpaceLayout>
20  );
21}

Render Props -- even greater flexibility

Render Props is a pattern where a component accepts a function as a prop and uses it for rendering. This gives full control over what gets displayed:

1function DataFetcher({ url, render }) {
2  const [data, setData] = useState(null);
3  const [loading, setLoading] = useState(true);
4
5  useEffect(() => {
6    fetch(url)
7      .then(res => res.json())
8      .then(d => { setData(d); setLoading(false); });
9  }, [url]);
10
11  return render({ data, loading });
12}
13
14// Usage -- YOU decide how to display the data
15function MissionPage() {
16  return (
17    <DataFetcher
18      url="/api/missions"
19      render={({ data, loading }) => {
20        if (loading) return <p>Loading missions...</p>;
21        return <MissionList missions={data} />;
22      }}
23    />
24  );
25}

Render Props allows for great flexibility, but today it's often replaced by custom hooks, which achieve the same effect in a simpler form.

Composition vs inheritance -- summary

| Feature | Inheritance | Composition | |---------|-------------|-------------| | Flexibility | Rigid hierarchy | Free combination | | Reusability | Limited | High | | Testing | Difficult (dependencies) | Easy (isolation) | | React support | None | Full |

Key principle: If you want to reuse logic between components, use composition, not inheritance. React was designed with composition in mind -- every component is an independent module that can be freely combined with others. It's like building a spaceship from interchangeable parts -- each part works on its own, but together they create a powerful machine.

Go to CodeWorlds