Questions
This legacy class component mixes logic and UI. Refactor it to a modern functional component using Hooks.
You’ve been tasked with modernizing a part of a legacy React codebase. You find an old class component, UserListContainer, that fetches a list of users and renders them.
This component mixes data fetching, state management, and rendering logic all in one place. This makes the UI difficult to reuse or test in isolation. It follows the old “Container Component” pattern, but it’s time for an update.
Refactor this legacy component. Your goal is to separate the logic from the presentation using modern React Hooks.
- Create a custom Hook called
useUsersto encapsulate the logic for fetching and managing the user data. - Create a purely presentational component called
UserListthat receives the users and loading state as props and is only responsible for rendering the UI. - Create a new
UserPagecomponent that uses youruseUsershook and yourUserListcomponent to render the final output.
The final UI should look and function identically to the original.
import React, { Component } from 'react';
// Mock API call
const fetchUsers = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
]);
}, 1000);
});
};
// The legacy class component that mixes concerns
export default class UserListContainer extends Component {
state = {
users: [],
loading: true,
};
componentDidMount() {
fetchUsers().then(users => {
this.setState({ users, loading: false });
});
}
render() {
const { users, loading } = this.state;
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>
);
}
} How Different Experience Levels Approach This
I would convert the class component to a functional component and move all the code into the function body, using useState and useEffect for the state and lifecycle methods.
I would extract the stateful logic into a reusable custom hook called `useUsers` that encapsulates the data fetching with useState and useEffect. Then, I'd create a pure presentational component `UserList` that only handles rendering. Finally, a `UserPage` component would compose these together, achieving proper separation of concerns and making both the logic and UI independently reusable and testable.
The original code uses the Container and Presentational Component Pattern. The UserListContainer is a “smart” container that knows how to fetch data and manage state. It then renders the UI directly. The problem is that the UI (the ul and li elements) is not reusable without the data-fetching logic.
With the introduction of React Hooks, this pattern has evolved. We can achieve a much cleaner separation of concerns by extracting all the stateful logic into a custom Hook.
useUsersHook: This hook is our new “container.” It handles theuseStateforusersandloading, and theuseEffectfor fetching the data. It returns the stateful values. This logic is now reusable by any component.UserListComponent: This becomes our “dumb” presentational component. It receivesusersandloadingas props and does nothing but render the UI. It’s now easy to test and reuse.UserPageComponent: This component brings the two together. It calls theuseUsershook to get the data and then passes that data to theUserListcomponent to be rendered.
This approach is more flexible, composable, and aligns with modern React best practices.
Here is the corrected implementation:
import React, { useState, useEffect } from 'react';
const fetchUsers = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
]);
}, 1000);
});
};
// 1. The custom Hook for logic
const useUsers = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers().then(fetchedUsers => {
setUsers(fetchedUsers);
setLoading(false);
});
}, []); // Runs once on mount
return { users, loading };
};
// 2. The dumb presentational component
const UserList = ({ users, loading }) => {
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>
);
};
// 3. The final component bringing them together
export default function UserPage() {
const { users, loading } = useUsers();
return <UserList users={users} loading={loading} />;
} - 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 benefit of separating data logic from UI presentation in a React application?