Recently I realize I like this pattern a lot, much enough that I'll write a short note on this.
Problem
When we use React's Context, we need to first create the context:
export const ContextA = React.createContext(null);
Then <Context.Provider />
is normally called elsewhere. We'd probably import the context, grab the named Provider inside, and mount that <Provider />
with a value that we as a user needs to know. And likely higher in our component tree we have many of these:
const App = () => (
<PlaylistContext.Provider value={mySpotifyPlaylist}>
<CoffeeContext.Provider value={!!morning ? 'americano' : 'flatwhite'}>
<WorkspaceContext.Provider value={code}>
...now i can start working
</WorkspaceContext.Provider>
</CoffeeContext.Provider>
</PlaylistContext.Provider>
);
Approach
What makes more sense to me is that the developer who works on the Context probably knows all the things about it. Here is something I learned from Jamie's hook-based Unstated library. Instead of exporting the bare bone Context, we can wrap and re-export the context provider. This allows the author of the context to keep all the logic along the same conceptual object in one place.
export const PlaylistContext = React.createContext(null);
export const PlaylistProvider = ({ children }) => {
// potentially code to create "value"
const playlist = ['Bach Cello Suite No.1 in G Major, BWV1007'];
return (
<PlaylistContext.Provider value={playlist}>
{children}
</PlaylistContext.Provider>
);
};
Now in our app root, or anywhere we need to use the context, we no longer need to generate and supply that value elsewhere. We can write very terse context providers like this:
const App = () => (
<PlaylistProvider>
<CoffeeProvider>
<WorkspaceProvider>...my day of work</WorkspaceProvider>
</CoffeeProvider>
</PlaylistProvider>
);
There is one more motivation where this is really useful is that when you wrap the provider in your customized component, it then will show up in the DevTools with its own display name (first row in the screenshot below). This makes it easier for you to search / filter it, whereas if you use the Provider
directly from the context object you created, all of them are displayed as Context.Provider
(as on the second row):
Finally, if you are with TypeScript and have complaints around annotating the context value with a potential null
when creating the context, check out this solution in React TypeScript Cheatsheet.