11

I have a path.json file that contains the path of a component

// path.json

{
  "main": "./login/index.js",
  "paths": [
    {
      "name": "login",
      "path": "./login/index.js",
      "image": ""
    }
  ]
}

I want to load './login/index.js' file dynamically in react native and render this particular file

My current implementation

const MyComponent = createLazyContainer(() => {
  const componentPath = PathJson.main; // ./login/index.js
  return import(`${componentPath}`); //import error here @ line 7
});

export default MyComponent;

I am getting following error :

Invalid call at line 7: import("" + componentPath)

AmerllicA
  • 29,059
  • 15
  • 130
  • 154
Muhammad Iqbal
  • 1,394
  • 1
  • 14
  • 34

5 Answers5

12

What people have been telling you in the thread is correct but I'd like to add one possible solution. All the imports/require are resolved at compilation time and not at running time which you are trying to do. By the time you are running your app, if you haven't imported the files, you can't use them.

There is a workaround tho, assuming that you know all the files that you might in advance which is to do something like a factory:

const possiblePaths = {
    'one': require('path/to/file/1'),
    'two': require('path/to/file/2')
}
    
function(type){
    return possiblePaths[type];
}

And then you use it somehow like:

render(){
    const MyComponent = function('one');

    return <MyComponent/>;
}

This is more or less pseudo code and my not work right away, but hopefully yo get the idea. You need to store a reference to each of the imports you might need and then dont use the import, use the reference that was created for you at compilation time.

Community
  • 1
  • 1
sebastianf182
  • 9,844
  • 3
  • 34
  • 66
4

Actually, the React Native development concerns are not like development for the Web.

Just for this reason, it is not so important at all to have lazy loading in the production of a react-native project. Just import anything you want and then use them in any files of the project. all of them are in the bundle of production and exactly it is not important at all.

So for this problem, I prefer to have a helper file to collect all selectable libraries and export them:

// helper file
export { default as Index } from './Login';
export { default as OtherComponent } from './OtherComponent';

Then when you wanna use:

import { Index, OtherComponent } from 'helper';

~~~

render() {
  const MyComponent = someCondition ? Index : OtherComponent;

  return (
    <MyComponent />;
  );
}
AmerllicA
  • 29,059
  • 15
  • 130
  • 154
2

Solution:

const allPaths = {
  path1: require('file path1').default,
  path2: require('file path2').default
};
 render(){
  const MyComponent = allPaths["path1"];

  return <MyComponent/>
 }


Muhammad Numan
  • 23,222
  • 6
  • 63
  • 80
1

In React Native all the files that are being imported are bundled together, only those files can be dynamically imported.

Let's say you have three files index.js, test_1.js and test_2.js and if you have imported only test_1.js in index.js than React Native will only bundle those two files leaving test_2.js.

So to answer your question even if dynamic import works in React Native but because these files are not part of the bundle you are not able to import them.

fayeed
  • 2,375
  • 16
  • 22
  • So if one does import test_1.js and test_2.js in index, but not use them, then they would be bundled. And if then one tries to import(`${componentPath}` where component path is i.e the path for test_2.js, then there would be no error and the dynamic import would work successfully? – angelos_lex Mar 15 '20 at 01:00
  • Yes, the metro bundler needs to know all the files that will be used at compile time. – fayeed Mar 15 '20 at 04:43
0

I've once been in a similar situation where I need to do imports by variable, but that is limited to importing components inside a component and it uses code-splitting (Edit: I'm playing around to look for a solution without relying on code-splitting, I just realized there was a react-native tag in the question, and I don't think code-splitting is a good choice to go with in RN). I'm not sure by how much my method could help you, but here goes.

Side notes:

  • Importing folder that contains an index.js(jsx|ts|tsx) file should automatically resolve to that index file.
  • Importing from from './login/index.js' usually throws a 'Module not found' error. Either import from './login/index' or from './login but I prefer the last one as it's the shortest & simplest.


In path.json:

{
  "main": "./login", // '.js' is removed
  "paths": [
    {
      "name": "login",
      "path": "./login/index.js", // Not sure what this is for, but if necessary, remove the '.js' here as well
      "image": ""
    }
  ]
}


In MyComponent.js:

import React, { lazy, Suspense } from 'react'
import PathJson from './path'

// 1. We need a UI to show while component is being loaded
const Loader = () => <div>{'Loading...'}</div>

// 2. We need a fallback UI if component is not found
const DocUnavailable = () => <div>{'We\'re sorry, but this document is unavailable.'}</div>

// 3. Create a resolver function ('resolver' is just a name I give)
function resolveImport(pathToComponent, FallbackComponent) {
  let componentFound = false
  let RenderComponent = () => <FallbackComponent /> // Assign fallback first
  try {
    if (require.resolve(pathToComponent)) {
      componentFound = true
    }
  } catch (e) { } // Kinda hacky, if you don't mind, but it works
  if (componentFound) {
    // If found, replace fallback with the valid component
    RenderComponent = lazy(() => import(pathToComponent))
  }
  return RenderComponent
}

// 4. Finally, implement it in a component
class MyComponent extends React.Component {

  render() {
    const componentPath = PathJson.main
    const RenderComponent = resolveImport(componentPath, DocUnavailable)
    return (
      <Suspense fallback={<Loader />}>
        <RenderComponent />
      </Suspense>
    )
  }

}

export default MyComponent


References:

GlyphCat
  • 144
  • 1
  • 3
  • 15