Container Components: One Place for Data, Many Places to Display It

Every component that fetches its own data creates a duplication problem. Container components fix this by extracting data loading into a wrapper that passes results down automatically.

June 27, 20263 min read2 / 3

The design patterns overview set up the problem. Now here is the first one that actually changes how you structure a real app.

I used to think the right place for a useEffect data fetch was inside the component that displays the data. That felt natural. UserProfile fetches user data. BookCard fetches book data. Each component is self-contained.

Then a second component needed the same user data.

And there was no clean way to share it.

Duplicating the useEffect in both components is wrong. Lifting the data to a parent and passing it as props works, but now the parent knows too much. What you actually want is a component whose only job is loading data and handing it to whatever child needs it.

That is a container component.

What It Does

A container component fetches data. It then clones its children and injects the fetched data as a prop onto each one. The children never call useEffect. They just receive props and render.

JSX
const CurrentUserLoader = ({ children }) => { const [user, setUser] = useState(null); useEffect(() => { (async () => { const response = await axios.get('/current-user'); setUser(response.data); })(); }, []); return ( <> {React.Children.map(children, child => { if (React.isValidElement(child)) { return React.cloneElement(child, { user }); } return child; })} </> ); };

React.Children.map iterates over the children. React.cloneElement clones each valid element and attaches user as an additional prop.

The child component receives user without knowing where it came from:

JSX
const UserProfile = ({ user }) => ( <div> <h2>{user?.name}</h2> <p>{user?.email}</p> </div> ); <CurrentUserLoader> <UserProfile /> </CurrentUserLoader>

Why This Works as a Layout Component Parallel

The layout components chapter established that content components should not know where they render. This pattern applies the same idea to data: content components should not know where their data comes from.

UserProfile is just a display component. It accepts a user prop and renders it. Whether that prop came from a container, a parent, a Redux store, or a test fixture does not matter. The component stays the same.

A Note on React.cloneElement

cloneElement injects props silently. Someone reading <UserProfile /> in the JSX does not immediately see where user comes from. That can be confusing.

Use it deliberately, and only when the container pattern is the explicit design. In a container component, it is appropriate because the whole point is that the container owns the data and the children display it. The structure makes the intent clear.

For everything else, pass props explicitly.

The next post takes this container and generalizes it until it works with any URL, any data type, and any source, not just the current user endpoint.

The Essentials

  1. A container component fetches data and passes it down. Display components receive data and render it. They should never mix.
  2. React.Children.map + React.cloneElement lets the container inject props onto any valid child element.
  3. Display components stay identical whether their data comes from a container, a parent, or a test fixture.
  4. Use cloneElement deliberately, in explicit container patterns. Do not use it as a general prop-shortcut.

Further Reading and Watching

Practice what you just read.

Container Component Quiz
1 exercise