React lazy recursive import

Viewed 1462

I have requirement where I need to load component from dynamic folders. For example I have following folders inside components

components  
    -default
        -component-one
        -component-two
        -component-three
    -custom
        -component-three

Suppose if componentFolder state set to custom folder then it should load from custom folder .if any component not found in custom folder then it should be load from default folder. So my question is ,can we possible to import recursively ?

 function App() {
 
const [componentFolder, setComponentFolder] = React.useState("default")

const Home = React.lazy(() => import("./components/" +componentFolder+ "/Home"));
  return (
    <div className="App">
      <Suspense fallback="laoding">
        <Home></Home>
      
      </Suspense>

    </div>
  );
}

the below link has same requirement as i asked How to check if a pariticular fileExists in reactjs

5 Answers

If you are using Webpack then you can use require.context to load modules dynamically:

import React, { Suspense } from "react";

const load = async (path, file) => {
  const defaultPath = "default";
  const files = require.context("./components", true, /\.js$/);
  try {
    return files(`./${path}/${file}.js`);
  } catch (err) {
    return files(`./${defaultPath}/${file}.js`);
  }
};

export default function App() {
  const [componentFolder, setComponentFolder] = React.useState("default");
  const Home = React.lazy(() => load(componentFolder, "Home"));
  return (
    <div className="App">
      <Suspense fallback="loading">
        <Home />
      </Suspense>
    </div>
  );
}

Since lazy returns a promise, you can use its catch block to return another lazy (promise) when the original module was not found.

An example:

import { lazy, Suspense, useState } from "react";

const rotate = {
  custom: "default",
  default: "custom",
};

function App() {
  const [folder, setFolder] = useState("custom");
  const [name, setName] = useState("component1");

  // Here: catch and return another lazy (promise)

  const Component = lazy(() =>
    import("./components/" + folder + "/" + name).catch(
      (err) => import("./components/" + rotate[folder] + "/" + name)
    )
  );

  return (
    <div>
      <Suspense fallback="laoding">
        <Component />
      </Suspense>
      <button onClick={() => setFolder(rotate[folder])}>toggle folder</button>
      <br />
      <button onClick={() => setName("component1")}>load component 1</button>
      <button onClick={() => setName("component2")}>load component 2</button>
      <button onClick={() => setName("component3")}>load component 3</button>
    </div>
  );
}

Here is a demo.


Note that Component, defined/created inside App component, will be recreated at every rerender of App. It will cause Component to reset its state when App rerenders.

Based in the others answers and comments here I came up with this:

https://codesandbox.io/s/so-react-lazy-recursive-import-2dqlp?file=/src/App.js

import React, { lazy, Suspense } from "react";

// test the code by removing the _ in front of file names

/*
components/
  comp  3? a root file will not trigger -> go to default
  
  /default
    comp  3! nice ^^ (but if it not exists will throw an error)

  /custom
    comp  2?
    /client
      comp  1?
        /omgStop
          heIsAlreadyDead (but works)
    /otherClient ...
*/

const recursiveImport = async (
  componentName,
  targetTree,
  defaultTree = "./components/default"
) => {
  console.count("paths tested");
  if (!targetTree) {
    return import(defaultTree + "/" + componentName);
  }

  return import("./components/" + targetTree + "/" + componentName).catch(
    () => {
      const newTreeArr = targetTree.split("/");
      newTreeArr.pop();
      const newTree = newTreeArr.join("/");
      return recursiveImport(componentName, newTree, defaultTree);
    }
  );
};

export default function App() {
  const targetTree = "custom/client1";
  const Component = lazy(() => recursiveImport("Test", targetTree));

  return (
    <div>
      <Suspense fallback="loading">{<Component />}</Suspense>
    </div>
  );
}

Folder structure:

enter image description here

This solves all of your requirements?

Simple and objective

const Recipe = React.lazy(() =>
  import(`docs/app/Recipes/${props.componentName}`)
  .catch(() => ({ default: () => <div>Not found</div> }))
);
Related