When working with React, especially on larger or long-term projects, it's easy to end up with messy, repetitive, or hard-to-maintain code. But React gives us patterns (simple structures and ideas) that make our code easier to read, reuse, and scale.
In this article, we’ll explore 5 React component patterns every developer should know. These aren’t frameworks or libraries. They’re smart ways of organising your code that work beautifully with React’s component-based structure.
1. Presentational vs Container Components
One of the easiest and most powerful ideas to start with is splitting your components into two types:
- Presentational Components: Focus on how things look (UI), are stateless, and receive data via props.
- Container Components: Handle state, logic, and data. Focus on how things work.
// Presentational Component
const UserCard = ({ name, onClick }) => (
<div onClick={onClick}>
<h2>{name}</h2>
</div>
);
// Container Component
const UserContainer = () => {
const name = "Ali";
const handleClick = () => alert(`Hi, ${name}`);
return <UserCard name={name} onClick={handleClick} />;
};This pattern helps you reuse UI components and keep your code clean and testable.
2. Higher-Order Components (HOC)
A Higher-Order Component is a function that takes a component and returns a new one, with extra features or props.
Commonly used in libraries like React Redux and React Router.
const withLoading = (Component) => {
return function WithLoadingComponent({ loading, ...props }) {
if (loading) return <p>Loading...</p>;
return <Component {...props} />;
};
};
// Usage
const UserList = ({ users }) => (
<ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>
);
const UserListWithLoading = withLoading(UserList);3. Render Props
A Render Prop is a function prop that a component uses to know what to render.
const MouseTracker = ({ render }) => {
const [pos, setPos] = React.useState({ x: 0, y: 0 });
const handleMove = (e) => setPos({ x: e.clientX, y: e.clientY });
return <div onMouseMove={handleMove}>{render(pos)}</div>;
};
// Usage
<MouseTracker render={(pos) => <p>Mouse at {pos.x}, {pos.y}</p>} />;This gives full flexibility over how the component displays data.
4. Compound Components
Compound components allow you to build related components that work together and share internal state.
const Tabs = ({ children }) => {
const [activeIndex, setActiveIndex] = React.useState(0);
return React.Children.map(children, (child, index) =>
React.cloneElement(child, {
isActive: index === activeIndex,
onClick: () => setActiveIndex(index),
})
);
};
const Tab = ({ label, isActive, onClick }) => (
<button onClick={onClick} style={{ fontWeight: isActive ? "bold" : "normal" }}>
{label}
</button>
);
// Usage
<Tabs>
<Tab label="Tab 1" />
<Tab label="Tab 2" />
</Tabs>This is perfect for things like tabs, modals, or dropdown menus.
5. Custom Hooks
Custom hooks let you extract logic into reusable functions using useState, useEffect, etc.
function useToggle(initial = false) {
const [value, setValue] = React.useState(initial);
const toggle = () => setValue((v) => !v);
return [value, toggle];
}
// Usage
const Switch = () => {
const [isOn, toggle] = useToggle();
return <button onClick={toggle}>{isOn ? "ON" : "OFF"}</button>;
};This keeps components clean and promotes DRY code.
Why These Patterns Matter
- Scalable architecture
- Reusable logic and components
- Cleaner, testable code
- Easier team collaboration
These patterns make a real difference when working on real-world projects, from solo builds to large enterprise apps.
Trade-offs at a Glance
Every pattern solves one problem and introduces another. This is the short version:
| Pattern | Strength | Watch out for |
|---|---|---|
| Presentational vs Container | Logic and UI are separate, so each is easier to test and reuse | More components and some boilerplate |
| Higher-Order Components | Adds the same behaviour to many components without changing them | Layers of wrappers make debugging harder, and props can clash |
| Render Props | Full control over what gets rendered with shared logic | Deep nesting that becomes hard to read |
| Compound Components | A clean API for related pieces such as tabs or dropdowns, without prop drilling | Harder to build correctly |
| Custom Hooks | Reusable stateful logic that keeps components small | Too many tiny hooks can scatter logic across files |
Here is a slightly bigger custom hook than the toggle above, for loading data:
function useFetch(url) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
let cancelled = false;
fetch(url)
.then((res) => res.json())
.then((json) => {
if (!cancelled) {
setData(json);
setLoading(false);
}
});
return () => {
cancelled = true;
};
}, [url]);
return { data, loading };
}Related Post
New to React? Make sure you've mastered the basics: 7 JavaScript Concepts to Learn Before React
FAQs
Q1: Are these official React patterns?
No, but they’re widely accepted best practices in the React community.
Q2: Should I use all these patterns in one project?
Use the ones that fit your app’s needs. The goal is clarity and reusability.
Q3: Are hooks better than HOCs or render props?
Hooks simplify a lot of things, but each pattern has its use case.
Q4: What’s the easiest pattern to start with?
Start with Presentational vs Container: it’s simple and effective.
Q5: Are HOCs still relevant?
Yes, especially in older codebases and some libraries, but for new code custom hooks usually do the same job with less wrapping.
Q6: Custom hook or render prop?
Use a custom hook to share stateful logic. Use a render prop when the caller needs to decide what gets rendered with that logic.
Conclusion
React is powerful, but using the right patterns can make it even better.
By applying these 5 component patterns, you’ll write code that’s:
- Smarter
- Easier to test
- Ready to scale
Save this post. Bookmark it. And share it with your dev friends!
Want more? Browse more tutorials at bilalshafqat.com