Search code examples
javascriptreactjsdynamicloadable-component

Load function from external script using @loadable/component in React


I have a JSON file with several filepaths to scripts that I want to be able to load dynamically into my React app, to build each component based on specifications that are in the metadata. Currently I have the metadata in my app as a Metadata data object.

metadata.json:

{
  "component1": { "script": "./createFirstLayer.js" },
  "component2": { "script": "./createSecondLayer.js" }
}

Each script exports a function that I want to be able to use to construct the component. For troubleshooting purposes, it currently only returns a simple message.

function createFirstLayer(name) {
  return name + " loaded!";
}

export default createFirstLayer;

I did some research and identified the @loadable/component package. Using this package as import loadable from "@loadable/component";, I attempted to load my script into App.js like this:

  async componentDidMount() {
    Object.keys(Metadata).forEach(function(name) {
      console.log(Metadata[name].script);
      var createLayer = loadable(() => import(Metadata[name].script));
      var message = createLayer(name);
      console.log(message);
    });
  }

Everything I have tried throws the TypeError createLayer is not a function. How can I get the function loaded?

I have also attempted the lazy method.

I have recreated a working demo of my problem here.

EDIT: I have tried to put this at the top of my app

const scripts = {};
Object.keys(Metadata).forEach(async function(name) {
    import(Metadata[name].script).then((cb) => scripts[name] = cb);
});

This causes the TypeError Unhandled Rejection (Error): Cannot find module './createFirstLayer.js'. (anonymous function) src/components lazy /^.*$/ groupOptions: {} namespace object:66

I have also attempted

const scripts = {};
Object.keys(Metadata).forEach(async function(name) {
    React.lazy(() => import(Metadata[name].script).then((cb) => scripts[name] = cb));
});

My goal is to be able to call the appropriate function to create particular layer, and match them up in the metadata.


Solution

  • You don't need @loadable/component for two reasons.

    1. You can accomplish your goal with dynamic imports
    2. '@loadable/component' returns a React Component object, not your function.

    To use dynamic imports simply parse your JSON the way you were, but push the call to the import's default function into state. Then all you have to do is render the "layers" from within the state.

    Like this:

    import React, { Component } from "react";
    import Metadata from "./metadata.json";
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = { messages: [] };
      }
      async componentDidMount() {
        Object.keys(Metadata).forEach(name=> import(`${Metadata[name].script}`).then(cb =>
          this.setState((state, props) => ({ messages: [...state.messages, cb.default(cb.default.name)] }))));
      }
      render() {
        return (
          <div className="App">
            {this.state.messages.map((m, idx) => (
              <h1 key={idx}>{m}</h1>
            ))}
          </div>
        );
      }
    }
    
    export default App;
    

    Here is the working example