Container
What is a Container Component?
Containers are "smart" components that handle data fetching, state management, and business logic. They wrap "dumb" presentational components, connecting them to data sources (Redux, APIs).
Key Principles
Smart vs Dumb Separation: Containers know about Redux, API calls, business logic. Presentational components only know about props and rendering.
Data Orchestration: Containers fetch data, manage loading/error states, transform data, and pass to presentational components.
Reusability Through Separation: Presentational components become reusable across different data sources; containers can swap presentational components.
Best Practices
✅ DO:
- Connect to Redux store in containers
- Handle loading, error, empty states
- Transform API data to component props
- Keep containers focused on one feature
- Use custom hooks to extract container logic
❌ DON'T:
- Put data fetching in presentational components
- Mix styling concerns in containers
- Create containers that are too large (split them)
- Duplicate logic across similar containers (extract hooks)
Code Patterns
Container/Presentational Pattern
// TodoListContainer.jsx - SMART (Container)
import { useSelector, useDispatch } from 'react-redux';
import { fetchTodos, toggleTodo } from '../state/todoSlice';
import TodoList from '../components/TodoList'; // DUMB
const TodoListContainer = () => {
const dispatch = useDispatch();
const { todos, loading, error } = useSelector(state => state.todos);
useEffect(() => {
dispatch(fetchTodos());
}, [dispatch]);
const handleToggle = (id) => dispatch(toggleTodo(id));
return (
<TodoList
todos={todos}
loading={loading}
error={error}
/>
);
};
// TodoList.jsx - DUMB (Presentational)
const TodoList = ({ todos, loading, error, onToggle }) => {
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!todos.length) return <EmptyState />;
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
=> onToggle(todo.id)}
/>
))}
</ul>
);
};
Modern Hook-Based Pattern
// Custom hook extracts container logic
function useTodos() {
const dispatch = useDispatch();
const { todos, loading, error } = useSelector(state => state.todos);
useEffect(() => {
dispatch(fetchTodos());
}, [dispatch]);
const toggleTodo = (id) => dispatch(toggleTodoAction(id));
return { todos, loading, error, toggleTodo };
}
// Container becomes thin wrapper
const TodoListContainer = () => {
const { todos, loading, error, toggleTodo } = useTodos();
return (
<TodoList
todos={todos}
loading={loading}
error={error}
/>
);
};
Testing Benefits
// Presentational component easy to test
test('TodoList renders items', () => {
const mockTodos = [{ id: 1, text: 'Test', completed: false }];
render(<TodoList todos={mockTodos} />);
expect(screen.getByText('Test')).toBeInTheDocument();
});
// No Redux mocking needed for presentational!
Related Terminologies
- Organism (UI) - Organisms often become containers
- Store (State) - Containers connect to store
- Component (UI) - Presentational components
- Selectors (State) - Containers use selectors
Quality Gates
- Data logic in container, rendering in presentational
- Loading/error/empty states handled
- Presentational components have no store connection
- Logic extracted to custom hooks when reusable
- Clear naming convention (*Container suffix)
Source: /docs/server/container.md