Skip to the content.

The container line: draw it, and defend it with a check

The container line: draw it, and defend it with a check

Somewhere in every component tree there is a line. Above it, components may touch the store, dispatch actions, and fetch data. Below it, components may only receive props and emit events. That is the container line, and most architectural health comes down to one question: is the line drawn clearly, and is it defended? Teams that keep the line sharp end up with a portable UI layer and a testable state layer. Teams that let it blur — a useSelector here, a fetch in an organism there — end up with components that only work in one place and cannot be tested without standing up the whole app.

The container line divides store-aware components from pure presentational ones A horizontal line splits the tree. Above it: page and container components that may read the store. Below it: organisms, molecules and atoms that only take props. the container line may touch the store ↑ page container props only ↓ organism molecule atom props
Everything above the line may know about the store; everything below receives props. The line is the seam where state becomes UI.

Naming the line makes reviews decidable

The line’s value is that it turns a vague preference (“keep components clean”) into a yes/no test any reviewer can apply: is this component above or below the line, and does its code match? An organism with a useSelector is a line violation, no debate required:

// BELOW the line — an organism must not read the store
function CommentList() {
  const comments = useSelector((s) => s.comments.all);   // ⚠ crosses the line
  return <ul>{comments.map((c) => <Comment key={c.id} {...c} />)}</ul>;
}

// CORRECT — a container above the line reads; the organism takes props
function CommentListContainer() {
  const comments = useSelector((s) => s.comments.all);
  return <CommentList comments={comments} />;              // ✓ line respected
}

Defend it with a check, not vigilance

A line defended only by review erodes, because review is intermittent and an AI generates line-crossing code faster than anyone reads it. So encode the line as a lint rule: nothing under your presentational directories may import the state layer.

// eslint: below-the-line directories may not import state
{
  files: ["src/ui/**/*.{js,jsx}"],           // organisms, molecules, atoms
  rules: {
    "no-restricted-imports": ["error", {
      patterns: ["**/store", "**/state/*", "react-redux"],
    }],
  },
}

Now a useSelector in an atom fails the build with a message pointing at the line, and the discipline holds without anyone having to remember it.

The line is where two skills meet

The container line is not a third concept alongside presentational/container and “organisms don’t fetch” — it is the same boundary named as a location. “Split by render-vs-decide” tells you which components go where; “organisms don’t fetch” tells you the fetch stays above; the container line is the drawable, checkable place all of that happens. Draw it explicitly (a directory convention, a naming rule), keep the store-aware code strictly above it, and enforce it with a check so volume cannot wear it down. Do that and your UI layer is a library you can lift into another app, and your state layer is a thing you can test without rendering a pixel. The presentational-vs-container exercise and the harness-state-shape check are the two halves of drawing and defending exactly this line.