Why an organism should not fetch its own data
An organism — a product grid, a comment thread, a sign-up form — feels big enough to “own” its data, and the tempting move is to let it fetch on mount. Resist it. The moment a reusable organism fetches its own data, it stops being reusable: it is now welded to one endpoint, one data shape, one loading policy, and one set of assumptions about when to load. Keep the fetch above the organism, in a container, and the organism stays a portable, prop-driven piece you can drop anywhere, render in Storybook, and test without a network. This is the single discipline that most determines whether your UI layer is a library or a pile of one-off features.
The welded version cannot be reused
Here is the organism that fetches. It works — once — for exactly the endpoint and shape it was written against, and it drags a loading state and an error state into a component whose job was to render a grid:
// BOUND: this ProductGrid can only ever show /api/products
function ProductGrid() {
const [products, setProducts] = useState([]);
useEffect(() => {
fetch("/api/products").then((r) => r.json()).then(setProducts);
}, []);
return <div className="grid">{products.map((p) => <Card key={p.id} {...p} />)}</div>;
}
Now try to use it for search results, or a category page, or a “related items” strip. You cannot — you would have to change the fetch, and there is only one.
The prop-driven version serves everyone
Hoist the fetch into a container and make the organism take products as a prop.
The organism no longer knows or cares where the data came from:
// PORTABLE: renders whatever list it is handed
function ProductGrid({ products }) {
return <div className="grid">{products.map((p) => <Card key={p.id} {...p} />)}</div>;
}
// containers supply different data to the SAME organism
const AllProducts = () => <ProductGrid products={useProducts()} />;
const SearchResults = ({ q }) => <ProductGrid products={useSearch(q)} />;
const RelatedItems = ({ id }) => <ProductGrid products={useRelated(id)} />;
One organism, three features. And in Storybook you render <ProductGrid
products={fixture} /> with no network at all.
The rule, and where it bends
The rule is crisp: data enters at the container line, above the organism, never
inside it. Fetching, useSelector, dispatching — all of it belongs at or above
that line, so everything below stays pure and portable. The one honest nuance is
that “container” is a role, not necessarily a separate file: a page component that
already has the data can compose the organism directly. What must not happen is
the organism reaching out to the world on its own, because that is the exact moment
it stops being a reusable piece and becomes a feature. This is also the boundary an
AI erodes fastest, since fetching-in-place is locally the shortest answer — which
is why it is worth a check, not just a convention. The presentational-vs-container
and atom-boundaries exercises are built around performing this hoist and defending
the line.