Presentational vs. Container
Problem
Components tightly couple UI rendering with data fetching, state management, and business logic, making them impossible to reuse with different data sources or test independently. Changes to the API or business rules require modifying components that should only care about presentation.
Solution
Separate components that render UI (presentational) from those that fetch data and manage state (container). This makes presentational components reusable and easier to test in isolation.
Example
This example shows separation of concerns: a presentational component that only renders UI, and a container component that handles data fetching and state management.
// Presentational component - only renders UI
// No data fetching, no business logic, just presentation
class UserList extends HTMLElement {
set users(value) {
this._users = value;
this.render();
}
render() {
// Pure rendering based on props
const items = this._users.map(u => `<li>${u.name}</li>`).join('');
this.innerHTML = `<ul>${items}</ul>`;
}
}
// Container component - handles data fetching
// Manages state and passes data to presentational component
class UserListContainer extends HTMLElement {
async connectedCallback() {
// Fetch data from API
const users = await fetchUsers();
// Pass data down to presentational component
const userList = this.querySelector('user-list');
userList.users = users;
}
render() {
this.innerHTML = '<user-list></user-list>';
}
}
Benefits
- Makes presentational components reusable across different data sources.
- Simplifies testing by allowing pure components to be tested without mocks.
- Creates clear separation between UI rendering and business logic.
- Enables designers to work on presentational components independently.
Tradeoffs
- Creates additional layers of components, adding boilerplate.
- Can be over-engineering for simple components that don’t need reuse.
- Makes component trees deeper and potentially harder to navigate.
- Largely superseded by hooks which provide better composition.