scott
scott

Reputation: 3192

React lazy recursive import

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

Upvotes: 3

Views: 2682

Answers (5)

Aloiso Gomes
Aloiso Gomes

Reputation: 830

Simple and objective

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

Upvotes: 0

Jorge Kunrath
Jorge Kunrath

Reputation: 1006

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?

Upvotes: 1

Vinicius Katata
Vinicius Katata

Reputation: 1051

I was trying something, endup with a simple solution that you should reach before:

https://codesandbox.io/s/awesome-violet-fr7np?file=/src/App.js

Upvotes: -2

lissettdm
lissettdm

Reputation: 13078

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>
  );
}

Upvotes: 4

Ajeet Shah
Ajeet Shah

Reputation: 19853

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.

Upvotes: 2

Related Questions