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.
Imagine you're building a spaceship. You have two approaches:
React intentionally does not support component inheritance. Instead, it gives us powerful composition tools.
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}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}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 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.
| 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.