Creating a Lazy React Components Provider

Asís García
Trabe
Published in
3 min readNov 11, 2019
Photo by Lance Anderson on Unsplash

In a previous story I wrote about using a components provider to customize your components. The code I shared in that story was a bit naïve (on purpose):

In this story I’ll try to tackle two problems that prevent the snippet above from being used in a production application.

Context-triggered re-renders

This first problem is not specific to a components provider, but a common issue that you must be aware of if you use React Context.

When you provide a value via context to some consumers, React will re-render those consumers whenever the provided value changes. If the value you share is an object, like in the code above, you have to be specially careful: building the value using a literal will result in a new (different) object being shared on every render. This causes a re-render of the consumers, every single time the provider renders.

You can use theuseMemo Hook to prevent this from happening (pay special attention to non-obvious object literals, like the one used as the default value when no custom components are passed to the provider 😉):

My colleague David Barral wrote a story about How we use React Context that you can read for more context-related “good practices”.

Code splitting

The components provider, as shown in the previous snippets, statically imports every component it provides. This means that your bundler of choice will bundle the provider and all the components it provides in a single file. In some scenarios, this can be a performance problem (particularly if you provide lots of components).

Using React.lazy you can overcome this problem: instead of providing the real components, the provider will expose lazy wrappers loaded via dynamic imports (at least for the less used or more “heavy” components):

You can then use Suspense to wait while the lazy components are loaded:

And if you use a capable bundler (Webpack, Parcel, rollup), the lazy components will be downloaded on demand from a different bundle file 😄.

Reducing the number of chunks

If you use dynamic imports to load every component you might end up with too many “bundles” (one file per imported component). You can mitigate this scenario using a dynamic import for a single module and cherry-picking the components you want to expose:

In the example above I’m supposing that your project has many simple components to build forms, all exported from a single index file in the forms directory, and that you want all of them to be bundled together in a single file.

You can achieve that dynamically importing the whole directory and then exposing each individual component as a lazy-loaded one.

There is a problem, though. The forms/index.js file in the previous example exports a bunch of components as named exports, but React.lazy only works when the import promise returns a module with a component as a default export:

React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component.

So, you can’t do something like:

checkbox: lazy(() => import("./forms").then(mod => mod.Checkbox)),

You have to return a “fake module” with a “default export”:

checkbox: lazy(() => import("./forms").then(mod => ({
default: mod.Checkbox
})),

Wrapping up

In this story we’ve seen how to use a couple of React’s tools (useMemo and lazy/Suspense) to enhance our components provider and make it more suitable for production use. Now it won’t cause unexpected re-renders, nor will it affect your bundle size 😀.

--

--