Sitemap

React Design Patterns with Real Examples

3 min readMay 10, 2025

--

Press enter or click to view image in full size
React Design Patterns

As a professional React developer, understanding and utilizing design patterns is essential for building scalable, maintainable, and efficient applications. Here, we will explore some of the most common React patterns and how they can enhance our development workflow.

1. Presentational and Container Components

When to use: Separate logic from UI to make components clean and testable.

Example:

// Container Component
function UserContainer() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(setUser);
}, []);
return <UserProfile user={user} />;
}

// Presentational Component
function UserProfile({ user }) {
if (!user) return <p>Loading...</p>;
return <h1>Welcome, {user.name}!</h1>;
}

2. Compound Components

When to use: Build flexible, declarative APIs for UI components like tabs or accordions.

Example: Tabs

const TabsContext = createContext();

function Tabs({ children, defaultIndex = 0 }) {
const [activeIndex, setActiveIndex] = useState(defaultIndex);
return (
<TabsContext.Provider value={{ activeIndex, setActiveIndex }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}

function TabList({ children }) {
return <div className="tab-list">{children}</div>;
}

function Tab({ index, children }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
return (
<button
className={index === activeIndex ? 'active' : ''}
onClick={() => setActiveIndex(index)}
>
{children}
</button>
);
}

function TabPanels({ children }) {
const { activeIndex } = useContext(TabsContext);
return <div>{children[activeIndex]}</div>;
}

function TabPanel({ children }) {
return <div>{children}</div>;
}

Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panels = TabPanels;
Tabs.Panel = TabPanel;

Usage:

<Tabs>
<Tabs.List>
<Tabs.Tab index={0}>Tab 1</Tabs.Tab>
<Tabs.Tab index={1}>Tab 2</Tabs.Tab>
</Tabs.List>
<Tabs.Panels>
<Tabs.Panel>Content for Tab 1</Tabs.Panel>
<Tabs.Panel>Content for Tab 2</Tabs.Panel>
</Tabs.Panels>
</Tabs>

3. Render Props

When to use: Share code between components using a function as a child.

Example:

import React, { useState, useEffect } from 'react';

// DataFetcher component that uses render props
const DataFetcher = ({ url, render }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
setData(result);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};

fetchData();
}, [url]);

return render({ data, loading, error });
};

// Parent component that uses DataFetcher
const App = () => {
return (
<DataFetcher
url="https://api.example.com/data"
render={({ data, loading, error }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h1>Fetched Data:</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}}
/>
);
};

export default App;

4. Higher-Order Components (HOC)

When to use: Add behavior to components without modifying them.

Example:

function withLogger(WrappedComponent) {
return function(props) {
console.log('Rendering', WrappedComponent.name);
return <WrappedComponent {...props} />;
};
}

const Hello = () => <h1>Hello!</h1>;
const HelloWithLogger = withLogger(Hello);

5. Custom Hooks

When to use: Extract logic from components to make them more reusable.

Example:

function useFetch(url) {
const [data, setData] = useState(null);

useEffect(() => {
fetch(url).then(res => res.json()).then(setData);
}, [url]);
return data;
}
// Usage
const posts = useFetch('/api/posts');

Conclusion

Incorporating these patterns into our React development can significantly improve the structure and quality of our applications. As we grow more familiar with these patterns, we’ll find that they can simplify our code, enhance reusability, and ultimately lead to a better development experience.

Happy coding!

--

--