Module 1: Routes and containers · Lesson 1 of 7 · 3 min read
Containers: where data meets the UI
Keep store reads, fetching and business rules in a thin container, so every component an agent writes below it stays props in, events out.
The split, as the templates ship it
Every chota-* template has a src/containers folder, and the files in it are
short. templates/chota-react-redux/src/containers/TodoListContainer.jsx reads
two values with useSelector — the selected filter, and the visible todos from
getVisibleTodos — wraps five dispatches in an events object, and returns one
line: <TodoList todoData={todoData} events={events} />. The Vue version,
templates/chota-vue-pinia/src/containers/TodoListContainer.vue, swaps the
selectors for two Pinia stores and keeps exactly the same todoData + events
hand-off. The TodoList organism underneath never imports the store.
State flows container to organism, in that direction only. The page above them,
src/pages/index.jsx in the React template, does even less: it places
SiteHeaderContainer, TodoListContainer and TodoFiltersContainer inside the
Layout template.
That is the whole definition. A container fetches, connects to the store, transforms API data into props and owns the loading, error and empty states. A presentational component renders the props it is given and calls back when the user does something.
What an agent gets wrong
Ask an agent for “a user list page” and it will usually make all three of the mistakes the Container doc names:
- Mixing data fetching with rendering. A
useEffectcallingfetchsits beside eighty lines of JSX, so the component cannot be rendered in a story without mocking the network. - Passing too many individual props. Ten loose props instead of one grouped
data object and one
eventsobject. - Containers rendering too much UI. The “thin” layer grew markup, so now it needs visual review as well as logic review.
The skill adds the rules that catch the rest: a container does not carry
styling, a container that grows too large is split, and logic duplicated across
similar containers is extracted into a custom hook instead of copied. Hooks such
as useTodos() can replace a container component, but the separation survives
the refactor: the code that fetches is not the code that renders.
Brief it, then review it
Create UserListContainer in src/containers. It reads users and the
request status from the store, dispatches the load on mount, and
renders the UserList organism with two props: userData (items,
isLoading, error) and events (onUserSelect). UserList must not
import the store or call fetch.
Then check what came back:
- Does any file under
src/ui/import fromsrc/state/or callfetch? That is a container in disguise. - Does the container return a single presentational component, with no styling of its own?
- Are loading, error and empty decided in the container and passed down as data?
- Does the name carry the
Containersuffix, and could the presentational component render in Storybook from hardcoded props alone?
Encode it, then practise it
SKILL.md that encodes Containers for a coding agent. Install it into your agent, or download it for any Agent-Skills-compatible runtime.
npx skills add grvpanchal/elegant --skill server-container
Practise it
Presentational vs container components
The split between a component that renders and a component that fetches, and what React 19 changed about defaults.
Course contents (7 lessons)
Module 1: Routes and containers
Module 2: Rendering strategies
- SSR: HTML per request, and the hydration bill done
- SSG: render once at build time, cache it right done
- App shell: cache the frame, stream the data done
Module 3: The network edge