Skip to the content.

Why an organism should not fetch its own data

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.

An organism that fetches is bound to one endpoint; one that takes props is reusable Left: an organism with a fetch inside it, chained to a single endpoint, not reusable. Right: a container fetches and passes items as a prop to the same organism, which now renders in many contexts. fetches itself organism + fetchwelded to /api/products one endpoint only takes props container fetches items={...} organism (pure) reused in app, marketing, Storybook
The fetch is what pins an organism to a single context. Move it up to a container and the same organism serves many.

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.