How to use import () to dynamically load components in React?

Viewed 4045

I tried to use import() instead of React.lazy to dynamically load components, but it didn't work.

App.js

import React, { useState } from 'react';
function App() {
  const [Com, setCom] = useState(null);

  const handleClick = () => {
      import("./A.js").then(c => {
        //console.log(c.default)
        setCom(c.default)
      }) 
  }

  return (
      <div>
        <button onClick={handleClick}>Load</button>
        { Com ? <Com /> : null }
      </div>
   );
}

export default App;

A.js

import React from "react";

export default function A () {
    return (<div>A</div>)
}

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

In fact, I printed out c.default. It's really a function.

c.default

ƒ A() {
  return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
    __source: {
      fileName: _jsxFileName,
      lineNumber: 4
    },
    __self: this
  }, "A");
}
2 Answers

Actually, it's not related to the dynamic import but the useState implementation. For example:

import Comp from "./A";

function App() {
  const [Com, setCom] = useState(Comp);

  return (
    <div>
      <Com />
    </div>
  );
}

Will throw the same error. That's because when you call useState(Comp), Comp "function" (which is the Component) been execute (you can test it by removing any <Com /> from the code and adding console.log inside A. The console.log still appears).

As a result Com is no longer a Component but a JSX element. When you try to render JSX element in a function way (meaning wrapping it with < and />, this error thrown.

The solution is either to set Com as a component (() => Com) or to render it as a JSX child ({Com})

import React, { useState } from "react";

function App() {
  const [Com, setCom] = useState(null);

  const handleClick = () => {
    import("./A.js").then(c => {
      //console.log(c.default)
      setCom(c.default);
    });
  };

  return (
    <div>
      <button onClick={handleClick}>Load</button>
      {/* <Comp /> */}
      {Com}
    </div>
  );
}

export default App;

https://codesandbox.io/s/answer-for-httpsstackoverflowcomq62125854863110-jj2wu

BTW, you can see the difference by console.dir the original outcome of import Comp from "./A"; and the outcome of useState(Comp)

import Comp from "./A";

function App() {
  const [Com, setCom] = useState(Comp);
  // console.log(1, Com, 2, Comp);
  console.dir(Com) // Object
  console.dir(Comp) // ƒ A()
  //...
}

If setState is called with a function, it will be executed with the previous value as the argument.

const [count,setCount] = useState(0)
setCount(prevCount => prevCount + 1)

In your case, rather than updating the state with the imported function, React will execute it and the result will be set as the state.

Solution:

setCom(() => c.default)
Related