0

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.

brienna
  • 1,415
  • 1
  • 18
  • 45

1 Answers1

3

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

Randy Casburn
  • 13,840
  • 1
  • 16
  • 31
  • thanks! I'm looking at this more closely, since I can't seem to get the message to print inside the componentDidMount – brienna Mar 09 '21 at 02:03
  • Yeah - there is a race condition. This does work, let me fix it. But I'm going to hide this answer until is works properly. – Randy Casburn Mar 09 '21 at 02:43
  • I updated my question with an attempt that I used based on your answer – brienna Mar 09 '21 at 02:43
  • That's odd...I'll take a look in a read IDE instead of the repl stuff. – Randy Casburn Mar 09 '21 at 02:44
  • OK, here is the entire solution that renders the layers into the view. Had to move the parsing back to `componentDidMount()` to over come the race condition. Note too, that I had to use template literal string for the variable component name. But this works as expected now. Sorry for the mix up. – Randy Casburn Mar 09 '21 at 03:07
  • It does work in the sandbox! Any idea why I'm getting locally this error: Unhandled Rejection (Error): Cannot find module './createFirstLayer.js' (anonymous function) src/components lazy /^.*$/ groupOptions: {} namespace object:66 – brienna Mar 09 '21 at 03:41
  • I cannot replicate that error. Are you sure you have used this: ``${Metadata[name].script}`` ? – Randy Casburn Mar 09 '21 at 04:00
  • Yes. this might be a separate problem. :( It works in codesandbox and I have the same requirements in my environment. I'm using create-react-app – brienna Mar 09 '21 at 04:52
  • aha fixed the path with the help of https://stackoverflow.com/a/54257036/4237080 – brienna Mar 09 '21 at 05:15
  • Yes, that is what my comment meant. Glad you got it sorted. – Randy Casburn Mar 09 '21 at 06:12