React Code Splitting in React.createElement()

Viewed 252

I have pages or large components that need to be rendered after the main page loads. I have them lazily loaded but am getting an error when I use in createElement():

LazyExoticComponent | LazyExoticComponent is not assignable to parameter of type 'string | FunctionComponent<RefAttributes> | ComponentClass<RefAttributes, any>'

What I've tested:

import Page2 from "./Page2";
import Page3 from "./Page3";
const pages = [Page2, Page3];

//in any method in the react component
let s = [Page2];
let ss = [Page2, Page3];
let sss = pages[0];
let r = React.createElement(s[0]); //this works
let rr = React.createElement(ss[0]); //does not work
let rrr = React.createElement(sss); //does not work

Using latest versions of Typescript, React, React-Scripts does not seem to have any affect.

Typescript ^3.9.9, React ^16.14.0, React-Scripts ^2.1.8, "@types/react": "^17.0.0", "react-dom": "^16.13.1", no @types/react-dom

2 Answers

This particular problem has to do with the generics of the React.Component type. If using an array, each element page's React.Component 'signature' has to be the same or it will throw this error. For example:

export default class Page2 extends React.Component {...}
export default class Page3 extends React.Component {...}

//in main file
const Page2 = lazy( ()=>import('./Page2') );
const Page3 = lazy( ()=>import('./Page3') );
const pages = [Page2, Page3];
...
let page = React.createElement(pages[1]);

This will work because the 'signatures' of Page2 and Page3 are the same. Namely, they are <>.

export default class Page2 extends React.Component<IPage2Props, IPage2State>
export default class Page3 extends React.Component<IPage3Props, IPage3State>

These on the other hand, will not work and are what caused the issue I posted about. Even if IPage3Props and IPage2Props have the same contents in the interface/type, it TS will still not understand.

This issue, as far as I can tell, does not have anything to do with the environment or the versions of TS, or React used.

The obvious solution is to make the 'signatures' the same. This may not be practical and I am unsure what else can be done to fix the issue.

You can simply use lazy and Suspense with a fallback content.

The lazy component should then be rendered inside a Suspense component, which allows us to show some fallback content (such as a loading indicator) while we’re waiting for the lazy component to load.

An example:

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

const Page2 = lazy(() => import("./Page2"));
const Page3 = lazy(() => import("./Page3"));

const pages = [Page2, Page3];

class App extends Component {

  render() {
    let s = [Page2];
    let ss = [Page2, Page3];
    let sss = pages[0];
    let r = createElement(s[0]);
    let rr = createElement(ss[0]);
    let rrr = createElement(sss);
    return (
      <>
        <h2>App</h2>
        <Suspense fallback="loading..."> {/* <== using the Suspense /*}
          {r} {rr} {rrr}
        </Suspense>
      </>
    );
  }
}

Here is CodeSandbox

Related