TL;DR: It’s best to understand both, and use them each in different situations.
Composition and Context are common ways to avoid prop drilling in React apps. Often times people argue for one or the other as an exclusive solution, but I think a more nuanced view is that they should both be understood, and used where they fit best. Composition works better for some things, and Context for others.
Where Composition Fits Best
In one app I’m working on, what seems to work best is using composition for components that are lower in the tree, and Context for ones that are higher. The simple, lower-level components that are just rendering small bits of UI based on props — e.g., ActiveUrlPreview
and SearchResults
— tend to be more generic and reusable, and it makes sense to pass their data in as props, so that they can remain uncoupled from the global state.
Composition also makes sense when you want to have specialized versions of generic components, like with Warning
and FetchErrorWarning
.
Where Context Fits Best
Composition doesn’t seem to make sense, though, for higher-level components that are essentially controller-views — stitching together generic components like Modal
and SearchResults
. For example, a controller-view like Loaded
could be pulled up into it’s parent MainView
, which is also a high-level controller-view. That would avoid an extra level in the hierarchy, but it would also result in MainView
being very cluttered, and doing too many things. Keeping all of the “loaded” state UI encapsulated inside Loaded
makes MainView
much easier to understand at a glance.
By their nature of being higher-level components, they’re inherently more aware of the app’s design and business logic, and are less likely to be reused, so I don’t mind coupling them to the global state.
In a larger application, though, it would probably still be best to have separate contexts for separate parts of the app, and only keep the truly universal data in the top-level context.
Results
Using Context and composition in this way removed the prop drilling from my app, while keeping the higher-level components simple, and the lower-level components reusable.
If anyone sees a better way, though, I’d be curious to hear your thoughts.
Side note: Hooks make working with Context so much easier. If you’ve tried using Context in the past and were turned off by render props and all that mess, you might be pleasantly surprised if you give it another shot.
Another side note: Over-using destructuring makes prop drilling even worse, because any time something changes you have to edit it in 2n
places instead of just n
. Sometimes it’s better to just reference props.foo
directly.