As you saw in the Provider and connect() readings,
there can be quite a bit of code involved in connecting a component to the
store. Putting all this code into the component with heavy rendering logic tends
to cause bloated components and violates the principle of separation of
concerns. Therefore, it's a common pattern in Redux code to separate
presentational components from their connected counterparts, called
containers.
The distinction between presentational components and containers is not technical but rather functional. Presentational components are concerned with how things look and container components are concerned with how things work.
Here's a table outlining the differences:
| Presentational | Container | |
|---|---|---|
| Purpose | How things look (markup, styles) | How things work (data fetching, state updates) |
| Aware of Redux | No | Yes |
| To Read Data | Read data from props |
Subscribe to Redux state |
| To Change Data | Invoke callbacks from props |
Dispatch Redux actions |
| Are Written | By hand | Generated by React-Redux connect() |
Not every component needs to be connected to the store. Generally, you will only want to create containers for the 'big' components in your app that represent sections of a page, and contain smaller purely functional presentational components. These larger container components are responsible for mapping state and dispatch props that can be passed down to all their presentational children.
Below is a project file tree that demonstrates how containers might be arranged in a folder for lists.
components
+ list
+ list_container.jsx
+ list.jsx
+ list_item.jsx
Notice that containers exist in the same folder as presentational components and that the container file is named list_container.jsx and not just list.jsx.
In general, aim to have fewer containers rather than more. Most of the components we will write will be presentational, but we'll need to generate a few containers to connect presentational components to the Redux store of our apps.
Let's take a close look at an example with presentational and container components. We'll use the example of lists from the file tree above.
// components/list/list_container.jsx
import { connect } from 'react-redux';
import { resetItems } from '../../actions/items' // action creator
import List from '../list'; // presentational component to connect
const mapStateToProps = (state) => ({ // map slice of state to props object
items: state.items
});
const mapDispatchToProps = (dispatch) => ({ // create action dispatcher
resetItems: () => dispatch(resetItems());
});
const ListContainer = connect(
mapStateToProps,
mapDispatchToProps
)(List);
export default ListContainer;ListContainer serves as an interface between the store and the component it
wraps. It's necessary to import List at the top of the file, as well as actions
we plan to dispatch.
// components/list/list.jsx
import React from 'react';
import Item from 'components/list/item';
const List = ({ items, resetItems }) => {
const listItems = items.map((item, idx) => (
<Item key={idx} item={item} />
);
return (
<div className="list">
<h1 onClick={resetItems}>
Click to Reset
</h1>
<ul className='list-items'>
{listItems}
</ul>
</div>
);
};
export default List;List receives props from ListContainer, deconstructed here as items and resetItems.
In List we pass as props individual items to Item, delegating the
responsibility of rendering individual list items to that component.
// components/list/item.jsx
import React from 'react';
const Item = ({ item }) => (
<div className="list-item">
<h3>
{item.name}
</h3>
<span>
{item.body}
</span>
</div>
);
export default Item;And finally, Item receives as props a single item, rendering that item's
name and body.