Questions
These components duplicate logic. Refactor them using a Higher-Order Component.
You’re reviewing a codebase and notice a common pattern: several components need to fetch data from an API and display a loading state while the data is being retrieved.
Specifically, you have UserDisplay and ProductDisplay components. Both implement their own useState for data and loading, and their own useEffect for fetching. This leads to duplicated code and makes maintenance harder.
Your task is to refactor these components. Create a Higher-Order Component (HOC) called withDataFetching that encapsulates the common data fetching and loading state logic.
Apply this HOC to both UserDisplay and ProductDisplay so that they become purely presentational components, receiving their data and loading props from the HOC.
import React, { useState, useEffect } from 'react';
// Mock API calls
const fetchUsers = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
}, 1000);
});
};
const fetchProducts = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve([{ id: 101, name: 'Laptop' }, { id: 102, name: 'Mouse' }]);
}, 1500);
});
};
// Component 1: Duplicates data fetching logic
function UserDisplay() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers().then(data => {
setUsers(data);
setLoading(false);
});
}, []);
if (loading) return <div>Loading users...</div>;
return (
<div>
<h2>Users</h2>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
// Component 2: Duplicates data fetching logic
function ProductDisplay() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchProducts().then(data => {
setProducts(data);
setLoading(false);
});
}, []);
if (loading) return <div>Loading products...</div>;
return (
<div>
<h2>Products</h2>
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
export default function App() {
return (
<div>
<UserDisplay />
<hr />
<ProductDisplay />
</div>
);
} How Different Experience Levels Approach This
import React, { useState, useEffect } from 'react'; // Mock API calls const fetchUsers = () => { return new Promise(resolve => { setTimeout(() => { resolve([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]); }, 1000); }); }; const fetchProducts = () => { return new Promise(resolve => { setTimeout(() => { resolve([{ id: 101, name: 'Laptop' }, { id: 102, name: 'Mouse' }]); }, 1500); }); }; // Component 1: Duplicates data fetching logic function UserDisplay() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetchUsers().then(data => { setUsers(data); setLoading(false); }); }, []); if (loading) return <div>Loading users...</div>; return ( <div> <h2>Users</h2> <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> ); } // Component 2: Duplicates data fetching logic function ProductDisplay() { const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetchProducts().then(data => { setProducts(data); setLoading(false); }); }, []); if (loading) return <div>Loading products...</div>; return ( <div> <h2>Products</h2> <ul> {products.map(product => ( <li key={product.id}>{product.name}</li> ))} </ul> </div> ); } export default function App() { return ( <div> <UserDisplay /> <hr /> <ProductDisplay /> </div> ); }
import React, { useState, useEffect } from 'react'; // Mock API calls const fetchUsers = () => { return new Promise(resolve => { setTimeout(() => { resolve([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]); }, 1000); }); }; const fetchProducts = () => { return new Promise(resolve => { setTimeout(() => { resolve([{ id: 101, name: 'Laptop' }, { id: 102, name: 'Mouse' }]); }, 1500); }); }; // The Higher-Order Component (HOC) function withDataFetching(WrappedComponent, fetchFunction) { return function WithDataFetching(props) { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetchFunction().then(fetchedData => { setData(fetchedData); setLoading(false); }); }, []); // Pass data and loading as props to the wrapped component return <WrappedComponent {...props} data={data} loading={loading} />; }; } // Refactored UserDisplay - now purely presentational function UserDisplay({ data, loading }) { if (loading) return <div>Loading users...</div>; return ( <div> <h2>Users</h2> <ul> {data.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> ); } // Refactored ProductDisplay - now purely presentational function ProductDisplay({ data, loading }) { if (loading) return <div>Loading products...</div>; return ( <div> <h2>Products</h2> <ul> {data.map(product => ( <li key={product.id}>{product.name}</li> ))} </ul> </div> ); } // Apply the HOC to enhance the components const EnhancedUserDisplay = withDataFetching(UserDisplay, fetchUsers); const EnhancedProductDisplay = withDataFetching(ProductDisplay, fetchProducts); export default function App() { return ( <div> <EnhancedUserDisplay /> <hr /> <EnhancedProductDisplay /> </div> ); }
- Context over facts: Explains when and why, not just what
- Real examples: Provides specific use cases from production experience
- Trade-offs: Acknowledges pros, cons, and decision factors
Practice Question
What is the primary purpose of a Higher-Order Component (HOC) in React?