Presentational Container Design Pattern
What is it
Presentational Container Component is a coding design pattern that consists of separating the logic from the presentation code. In a React context it works by splitting a Component into a Presentation part responsible for presenting the data to the user, generally using the JSX markup, and a Container part concerned with loading and processing the data before passing it to the UI.
This pattern helps organize the code and create reusable components that are easier to maintain and can be used in the entire application. It works well with Component Driven Development!
How to apply it
Most of the time, applying this pattern is about splitting complex components into two parts.
Presentation Components
These are responsible for how the user interface looks, generally they don't have any states and don't know how to load or update the data they render. Although they shouldn't store data, this doesn't mean they cannot have states, but when they do, the states should be related to the UI.
Container Components
They are concerned with how the things works and are responsible for handling the logic and specifying the data that a presentational component should render. Here is where you would store most of the states, fetch the data, dispatch actions, and so on.
Simple Example
Take the following component as an example:
export const MainTodoList = () => {
const dispatch = useDispatch();
const unsortedTodos = useSelector<StoreType, ToDo[]>(
store => store.todosReducer.todos
);
const todos = useMemo(() => [...unsortedTodos].sort(
(a, b) => a.completed && !b.completed ? -1 : 1
), [unsortedTodos]);
const handleCheck = useCallback((todo: ToDo) => {
dispatch(doUpdateTodo({ ...todo, completed: !todo.completed }));
}, [dispatch]);
const handleDelet = useCallback((todo: ToDo) => {
dispatch(doDeleteTodo(todo));
}, [dispatch]);
return (
<ul>
{todos.map(todo =>
<li key={todo.id}>
<button
active={todo.completed}
onClick={() => handleCheck(todo)}
>
<CheckIcon />
</button>
<span>{todo.title}</span>
<button onClick={() => handleDelet(todo)}>
<CloseIcon />
</button>
</li>
)}
</ul>
);
};
It is a single component that handles both data manipulation using a Flux pattern and renders the user interface for the list of Todos.
Here is one way that one could split the component into presentational and container parts:
Presentational
export const TodoList = ({ todos, onCheck, onDelete }) => (
<ul>
{todos.map(todo =>
<li key={todo.id}>
<button
active={todo.completed}
onClick={() => onCheck(todo)}
>
<CheckIcon />
</button>
<span>{todo.title}</span>
<button onClick={() => onDelete(todo)}>
<CloseIcon />
</button>
</li>
)}
</ul>
);
The presentational component is free of states and calls to the flux actions. It can easily be reused in other parts of the application and tested with tools like Storybook.
Container
export const MainTodoListContainer = () => {
const dispatch = useDispatch();
const unsortedTodos = useSelector<StoreType, ToDo[]>(
store => store.todosReducer.todos
);
const todos = useMemo(() => [...unsortedTodos].sort(
(a, b) => a.completed && !b.completed ? -1 : 1
), [unsortedTodos]);
const handleCheck = useCallback((todo: ToDo) => {
dispatch(doUpdateTodo({ ...todo, completed: !todo.completed }));
}, [dispatch]);
const handleDelete = useCallback((todo: ToDo) => {
dispatch(doDeleteTodo(todo));
}, [dispatch]);
return (<TodoList todos={todos} onCheck={handleCheck} onDelete={handleDelete} />
};
The Container contains all the logic and specificities of the data processing that should feed the presentational component. It does not need to worry about how the data will be displayed, only how to format the data so the component can render it.
How we use it today
Most of our recent projects use this pattern somehow, it is easier to develop components in isolation and employ a Storybook first workflow. The Phormar project is a great example of our use of the pattern. Our views are split into and index (Container) and template (Presentational) files. Take a look at the metrics index and template for example.
How to learn more
Useful Material
- Dan Abramov - Presentational and Container Components
- Using Presentational and Container Components with Redux
- Video - React Design Patterns: Presentational and Container Components
Validating your knowledge
- Refactor or create a simple project replacing complex components with presentational and container components.