React Advanced: Performance, Patterns and Production
Component Patterns
In this lesson you will
- Prefer composition over configuration with children and slots
- Build a compound component that shares state through context
- Choose between controlled and uncontrolled components
Introduction
Anyone can make a component work once. The skill is making components that other developers, including future you, can reuse and change without rewriting them. This lesson covers patterns that make components flexible.
Composition over configuration
A component with many options, such as showIcon, iconPosition, titleSize and footerButtons, becomes a tangle. Composition lets the caller decide what goes inside:
React (JSX)
// Hard to extend
<Card title="Plan" icon="star" footerText="Buy" onFooterClick={buy} />
// Flexible
<Card>
<Card.Header>
<StarIcon /> Plan
</Card.Header>
<Card.Body>Everything you need.</Card.Body>
<Card.Footer>
<button onClick={buy}>Buy</button>
</Card.Footer>
</Card>You can also pass elements as props, for named "slots":
React (JSX)
function Layout({ sidebar, children }) {
return (
<div className="layout">
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}Quick quiz
What is the main advantage of composition over lots of configuration props?
Compound components
A compound component is a set of components that work together and share hidden state, like HTML's select and option. Context carries the shared state:
React (JSX)
import { createContext, useContext, useState } from "react";
const TabsContext = createContext(null);
function Tabs({ defaultTab, children }) {
const [active, setActive] = useState(defaultTab);
return <TabsContext.Provider value={{ active, setActive }}>{children}</TabsContext.Provider>;
}
function Tab({ id, children }) {
const { active, setActive } = useContext(TabsContext);
return (
<button role="tab" aria-selected={active === id} onClick={() => setActive(id)}>
{children}
</button>
);
}
function Panel({ id, children }) {
const { active } = useContext(TabsContext);
return active === id ? <div role="tabpanel">{children}</div> : null;
}
Tabs.Tab = Tab;
Tabs.Panel = Panel;Usage reads naturally:
React (JSX)
<Tabs defaultTab="a">
<Tabs.Tab id="a">First</Tabs.Tab>
<Tabs.Tab id="b">Second</Tabs.Tab>
<Tabs.Panel id="a">Content A</Tabs.Panel>
<Tabs.Panel id="b">Content B</Tabs.Panel>
</Tabs>The user arranges the pieces freely, while the parts coordinate behind the scenes.
Render props and custom hooks
A render prop passes a function that decides what to draw:
React (JSX)
function MouseTracker({ children }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
return <div onMouseMove={(e) => setPos({ x: e.clientX, y: e.clientY })}>{children(pos)}</div>;
}
<MouseTracker>{(pos) => <p>{pos.x}, {pos.y}</p>}</MouseTracker>Today, most logic sharing is done more simply with custom hooks. Use render props when the logic and a bit of layout belong together.
Controlled and uncontrolled
- A controlled component's value is owned by its parent through props and callbacks, as with controlled inputs. The parent has full control, but writes more code.
- An uncontrolled component keeps its own state internally. It is simpler to use, but harder to coordinate.
Good reusable components support both: they work on their own with an optional defaultValue, and become controlled when you pass value and onChange.
Other habits of good components
- Single responsibility: one job each, with a clear name.
- Small, predictable props: avoid passing huge objects.
- Forward refs and extra attributes so users can attach classes, event handlers and accessibility attributes.
- Accessible by default: roles, labels and keyboard support built in.
- Document with examples, for instance in Storybook.
Try it yourself
Build an Accordion compound component with Accordion.Item, Accordion.Header and Accordion.Body, sharing state through context. Make it support both an uncontrolled defaultOpen and a controlled open plus onToggle.
Summary
- Prefer composition, using children and slots, over a growing list of options.
- Compound components share state through context so users can arrange the parts freely.
- Support both controlled and uncontrolled use, and build in accessibility.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading