June 10, 2026
Composition-proof components
A common pattern is a wrapper that injects data into its children. The old way to do it was cloneElement, passing extra props down into whatever you were handed. In a world of Server Components and lazy boundaries, that approach quietly falls apart.
The brittle version
Imagine a provider that wants every child to know who the current viewer is.
function CurrentUserProvider({ user, children }) {
return Children.map(children, (child) =>
cloneElement(child, { viewer: user }),
)
}
This breaks the moment a child is a Server Component, a lazy component, or a promise. Those children are opaque references. They are not plain elements you can clone and spread props onto, so React either throws or silently drops the prop.
The fix
Use Context. It passes data through the tree without ever touching the shape of the children, so it does not care what kind of element each child is.
const ViewerContext = createContext(null)
function CurrentUserProvider({ user, children }) {
return (
<ViewerContext.Provider value={user}>
{children}
</ViewerContext.Provider>
)
}
function FollowButton({ targetId }) {
const viewer = use(ViewerContext)
const isSelf = viewer?.id === targetId
return <button disabled={isSelf}>Follow</button>
}
Children stay opaque, the data still flows, and the wrapper composes with anything you hand it.