React: How to dynamically import modules at run time

Ok, I know a TON about this stuff.  A ton.  Literally, a ton.

 

And the web will confuse the heck out of you.  In the end, if you want to use Suspense and lazy() and import well that’s all up to you (I can help you correctly code any style).

 

My goal here/now is to explain the very base fundamentals and with examples that I think hit the very goal you wish, which is:

     Dynamically load some module whose name I might invent at-will (not a string constant; perhaps a variable and/or concats).

And I can’t see why this wouldn’t work with dynamically built modules now (I believe the core importing bundles the core parts of babel/webpack).

 

So, lets start with basic understanding of some of this stuff.

 

Let’s ignore the “import XYZ from ‘xyz’” at the top of modules.  That is implemented behind-the-scenes essentially.  We’re interested in run-time loading of modules.

 

There are a couple of different flavors.  You can use the API require() or the API import() (note: import() is *not* really the same as ‘import’ at the top of a module…import() is truly an API, just as is require()).

 

The two are close-enough.  ‘require()’ is synchronous.  ‘import()’ returns a promise.  For our example, I do this in my use-test-examples:

      let myModule=require(“./MyModule”);

You COULD do:

     let myModule=null;

     import(“./MyModule”)

          .then(module=>{

                  myModule = module;

             })

 

I like the first for this example…a tad simpler…but at run-time might cause a ‘pause’ if you’re loading in a complicated set of deps.

 

So, this now loads an “object” (module)…which could be ANYTHING the javascript world builds.  It could be a React component, or an array, or a class, etc etc.   For our example, let’s create Dyno1.jsx as:

import React from “react”;

 

export default class Dyno1 extends React.Component {

  render() {

    return <h1>Dyno1</h1>;

  }

}

 

A few things here are very important to keep in mind:

  • It’s a classic React-style component and thus imports React etc etc.
  • It creates a class which does the typical “extends React.Component” (I missed this doing some dumb test experiments and was pulling my hair out because the clues were horrible!!!!
  • It exports ONLY ‘default’

 

So, when you import or require this beast, you’re getting a “module”; typically you want an “item” in the module, like a class, like the default.  So, to import this example, I do:

  componentDidMount() {

    this.myDyno = require(“./Dyno1”).default;

    this.setState({ updateMe: true });

  }

 

In my main test component.   

Note that I select “.default” from the module. 

Note that I store the result in a class instance variable.

Note that I do a semi-fake setState() to cause my component to rerender (lots of diff ways to do this).

 

Now, imagine I also have “Dyno2” which is nearly like Dyno1 but I change the text it returns, ok?

And let’s say I want to dynamically load Dyno2 on a button click, ok? So I do this:

        <button

          onClick={e => {

            this.myDyno = require(“./Dyno2”).default;

            this.setState({ updateMe: true });

          }}

        >

          Click me to change to Dyno2 component

        </button>

 

Ok, with me?  I wont show you this, but I changed the above to *not* use the hard-constant “./Dyno2” but instead use a variable that I build-up at run-time just to prove that works (and it does). (Why did I bother? I was convinced at one point that webpack hunts out all import()/require()’s and does some prep work behind the scenes).

 

Now, in my test/example, I’m not using Suspense (because: I just don’t want to).

In my main component’s render method, I make a local variable that copies the class-instance-value of ‘this.myDyno’ like such:

    let MyDyno = this.myDyno;

Why do I do that?  Kinda a React thingy…because later I want to do something like <MyDnyo var1=”fish”/> and react likes user-created components to be capitalized and I’m not even sure if doing something like <this.myDnyo var1=”fish”/> would work because of the lower case ‘this’; regardless, its React-practice to use Pascal case.

 

Below is the full-example of my test “App” component.  It does these cool things:

  • In componentDidMount() it will dyno-load the Dyno1 component like you do
  • It keeps a hackish variable with the value ‘2’ in ‘state’ for run-time build-out of the “Dyno2” filename per my comment above
  • It has the “click button” to dyno load the Dyno2 component kinda like you want to
  • To render the current dyno OR a place holder (like suspense), very simple conditional expression shown below.

 

class App extends React.Component {

  myDyno = null;

  state = { updateMe: false, nextComponentNumber: 2 };

 

  componentDidMount() {

    this.myDyno = require(“./Dyno1”).default;

    this.setState({ updateMe: true });

  }

 

  render() {

    let MyDyno = this.myDyno;

    console.log(“***** render: MyDyno:”, MyDyno);

    return (

      <div>

        Main App<br></br>

        <button

          onClick={e => {

            this.myDyno = require(“./Dyno” +

              this.state.nextComponentNumber).default;

            this.setState({ updateMe: true });

          }}

        >

          Click me to change to Dyno2 component

        </button>

        {MyDyno ? <MyDyno /> : <h3>Not loaded</h3>}

      </div>

    );

  }

}

 

I wrote this up largely for the benefit of future generations.

 

Am I hot or what.  

Leave a Comment

Your email address will not be published. Required fields are marked *