Simple Dynamic Import of React Component from npm library

Hi everyone,

Somehow struggling to have a dynamic import in our Meteor / React app. Problem is quite simple, we’re using a npm package that exports a component. We use it only once in our whole application so would like it to be out of the bundle and just loaded when we reach the parent Component calling it. A simplication of the issue and what I tried is:

// CURRENT CODE:

import ComponentA from "@bla/A"
const ParentComponent = () => {
    (...)
    return (
        <div>
            (...)
            <ComponentA />
        </div>
    )
}

// I TRIED

const ParentComponent = async () => {
    const ComponentA = await import("@bla/A");
    (...) 
    return (
        <div>
            (...)
            <ComponentA />
        </div>
    )
}

and getting the following error:

I guess I am missing a point somewhere but couldn’t find such basic example in the various articles I found online recently.

Thanks in advance.

Edit; Bonus question: is this worth it if we deliver our bundle from a CDN anyway ?

I use react-loadable for this purpose, and it works great in my app, but I guess you could do a less sophisticated version with plain dynamic import as well. Maybe like this:

const ParentComponent = () => {
  const [ComponentA, setComponentA] = useState();
  useEffect(() => {
    import("@bla/A").then(setComponentA);
  }, []);
    return (
    <div>
      { ComponentA && <ComponentA /> }
    </div>
  )
}

[/quote]

3 Likes

@peterfkruger has already added the solution, but I thought I’d expand on the problem

So the error you’re seeing says that React couldn’t render your ParentComponent because it returned an Object, in this case a Promise object, instead of a React node or array of nodes.

This makes sense since all async functions return promises when run.

That means that your component needs to be a normal synchronous function.
You can see in Peter’s suggested solution that the function is sync, and uses state to update the component state when the import has finished loading

2 Likes

Thanks a lot for both your replies and clarification. Will test it soon.

Any idea regarding my question if it’s worth it if using a CDN (or on the other hand defeat the purpose?)

I think it definitely makes sense to use a CDN for that purpose; if the app gets little traffic, the effect may be negligible, but if you have lots of users, especially if geographically widely distributed, delivering the bundle from a CDN is almost a must.

1 Like

Thanks but my question (poorly phrased) was more the other way around: What I mean is: As I have already have a CDN is it worth to implement the dynamic import or actually it will take out the load from the CDN and move it to the server.

1 Like

You’re right, there’s a trade-off I didn’t even realize yet.

One could construct a case where the initial bundle is very small owing to excessive dynamic imports, in which case the burden of most of the delivery is still on the server – or the other way around, when the bulk of the code is in the initial bundle, and the typical user will get delivered just proportionally small additional dynamic imports.

Measurement is difficult, because once we start using dynamic imports, we can’t even use the bundle visualiser to determine the proportion of dynamic vs. static anymore – let alone the consideration of how much of the dynamic part will get actually loaded for individual user groups.

So I guess to remove from the bundle:

  • Rarely used libraries
  • Admin side of the app used by a small portion of the users

Would anyway make a smallest bundle, so faster to download from CDN as well, and it will “negatively” impact only these users which would need the admin or specific libraries

That was exactly my strategy too. Admins are just a small fraction of all users, and that’s ok if they need to wait another 1s for the dashboard and similar features to load.

The same with rarely used or particularly large libraries. Any package can be loaded dynamically, not just react; e.g. I dynamically load google-libphonenumber, which is ridiculously large (~580kb), in react components where specifically needed.

1 Like

I read that for the Admin part it’s enough to dynamically load the main AdminRoute and then all the subsequent component / libraries used will be removed of the bundle (if not used elsewhere) but when I tried this solution my bundle change didn’t change whatsoever… could you tell me how you handled it ?

Leveraging the Route component is the natural choice to make parts of your app being dynamically loaded.

When I had split off the admin part to make it dynamically loaded, I’ve experienced no difficulties, it worked as expected, so I went ahead and did the same for a handful of auxiliary pages too.

I did run into problems however with a number of other components, not Routes, however, and in all cases it turned out that they all were referenced elsewhere too.

Keep looking. Maybe it’s not your main AdminRoute that is taking up the space in the bundle, but some other heavy components within that you use elsewhere too.

1 Like