
Advanced React Patterns for Clean Component Design
When building React applications, the way you structure your components can make a significant difference in how maintainable and reusable your code will be. In this post, I'll cover some advanced patterns I've found extremely useful.
1. The Compound Component Pattern
Compound components are a pattern where components are used together to form a cohesive unit of functionality, but while maintaining a separation of concerns.
// Example implementation of compound component pattern
const Tabs = ({ children, defaultActiveTab }) => {
const [activeTab, setActiveTab] = useState(defaultActiveTab);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
{children}
</TabsContext.Provider>
);
};
Tabs.TabList = ({ children }) => <div className="tab-list">{children}</div>;
Tabs.Tab = ({ id, children }) => {
const { activeTab, setActiveTab } = useContext(TabsContext);
return (
<button
className={activeTab === id ? "active-tab" : "tab"}
onClick={() => setActiveTab(id)}
>
{children}
</button>
);
};
Tabs.TabPanels = ({ children }) => <div className="tab-panels">{children}</div>;
Tabs.TabPanel = ({ id, children }) => {
const { activeTab } = useContext(TabsContext);
return activeTab === id ? <div className="tab-panel">{children}</div> : null;
};
Usage:
<Tabs defaultActiveTab="tab1">
<Tabs.TabList>
<Tabs.Tab id="tab1">Tab 1</Tabs.Tab>
<Tabs.Tab id="tab2">Tab 2</Tabs.Tab>
</Tabs.TabList>
<Tabs.TabPanels>
<Tabs.TabPanel id="tab1">Tab 1 content</Tabs.TabPanel>
<Tabs.TabPanel id="tab2">Tab 2 content</Tabs.TabPanel>
</Tabs.TabPanels>
</Tabs>
2. The Render Props Pattern
Render props are a technique where a component receives a function as a prop, which it then calls with its internal state data.
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event) => {
setPosition({
x: event.clientX,
y: event.clientY,
});
};
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}, []);
return render(position);
}
Usage:
<MouseTracker
render={({ x, y }) => (
<div>
The current mouse position is ({x}, {y})
</div>
)}
/>
3. Custom Hooks for Logic Reuse
Custom hooks are the preferred way to share logic between components in React's ecosystem.
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
if (typeof window === "undefined") {
return initialValue;
}
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
if (typeof window !== "undefined") {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
Usage:
function App() {
const [name, setName] = useLocalStorage("name", "Guest");
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>
);
}
Performance Optimization
Beyond patterns, there are several techniques for optimizing performance in React applications:
1. Memoization with useMemo and useCallback
// Without memoization
const filteredUsers = users.filter((user) => user.active);
// With memoization
const filteredUsers = useMemo(
() => users.filter((user) => user.active),
[users]
);
2. Using React.memo for Component Memoization
const UserProfile = React.memo(function UserProfile({ user }) {
return (
<div>
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
});
Conclusion
By adopting these patterns, you can create React applications that are not only performant but also easier to maintain and extend. The key is to understand when each pattern is appropriate and to use them judiciously rather than trying to force them everywhere.
Remember that the best pattern is the one that makes your code more readable and maintainable in your specific use case. Don't apply patterns just for the sake of using patterns!
What advanced React patterns have you found most useful in your projects? I'd love to hear your thoughts in the comments below.