Imagine you are creating an accordion component that you want to distribute publicly through an npm package. You would like the user of this accordion to be able to use the component in a very flexible way, by composing multiple components together.
Imagine this is your ideal API:
AccordionItem will contain each section of the accordion that can be expanded or collapsed,
AccordionHeader will be the place where the user can click to expand or collapse, and
AccordionPanel will contain the content to be shown or hidden.
AccordionItem will need to maintain some state — whether it is expanded or not. But
AccordionHeader will also need access to this value, so that it can show the appropriate toggle button. And
AccordionPanel may also need to access this, since it is the thing being expanded and collapsed.
One possibility is exposing the expanded value to your user through render props and making sure your documentation lets them know they need to pass that down to the header and panel components.
While this may seem like a decent solution at first, it’s not ideal that the consumer of our component has to worry about the component internals. The fact that
AccordionPanel need access to the expanded state should not be something our user has to be concerned about.
It should also not be noted that while this is a trivial example, your component may be far more complex, with multiple levels of nested components, in which case prop drilling may become quite tedious.
Sick of debugging web apps? Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket pairs session replay with technical telemetry to quickly understand what went wrong.
What we really need is a way to implicitly pass down props.
Using React’s Context API
There is a better solution for cases like this — React’s Context API. We can use the Context API to create some state and provide it where needed behind the scenes, removing this concern from our public-facing API. (See also: Compound Components pattern).
First, we will create a context and define the shape of that context. We will start with an
expanded value and a
toggleExpansion method. We are defining this context as specifically relevant to our accordion item:
Now, inside our
AccordionItem component, we will define the
toggleExpansion values and feed them in as the value of the
Provider is one half of the Context equation. The other half is the
Provider allows the
Consumer to subscribe to context changes, as we will see soon.
Next, we need to set up
AccordionPanel as consumers of this context:
Consumer component requires a function as its child. This function will receive the context value, which we are destructuring into
toggleExpansion. Our component is then able to use these values in its template.
We will similarly use
Consumer to give
AccordionPanel access to the context value:
Now, we really can achieve our ideal API for the accordion component. Users of our component won’t have to worry about passing state up or down the component tree. Those component internals are hidden from them:
Provide/Inject in Vue
Vue provides a similar tool to React’s Context API, called provide/inject. To use this, we will use the
provide method on our
accordion-item Vue component:
We return an object from
provide() that contains the state we want to provide to other components. Note that we are passing an object to
accordionItemState, rather than simply passing the
expanded value. In order to be reactive,
provide must pass an object.
Note that we are using a render function here to create this component, but this is not necessary to use provide/inject.
Now, we will inject this state into our child components. We will simply use the
inject property, which accepts an array of strings corresponding the properties of the object we defined in
Once we include the property name in
inject, we have access to those values in our template.
Use with caution
It’s worth noting that you should only implicitly pass down props when it really makes sense. Doing this too much can obfuscate the real behavior of your components and cause confusion for other developers that may be working on your project.
A component library that is packaged up and distributed for use in other applications is a perfect use case for this, since the internal props of the components really don’t need to be exposed to the end user.
React’s Context API and Vue’s provide/inject feature both make it possible to do this through implicit state sharing.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.