Search code examples
typescriptlazy-loading

Typescript what is the type to pass for lazy import


I am new to typescript and I can't figure out how to solve this problem. I have some lazy imports in multiple files, and I want to wrap them all with a React.Suspense, so I thought that the best solution was this one:

generic .ts file with lazy imports

import Loadable from './Loadable'
import { lazy } from 'react'

const WrappedComponent = Loadable(lazy(() => import('path/to/my/component')));

...
// using wrapped component, for example:
// return <WrappedCompnent />
...

Loadable.tsx

import React, { Suspense } from 'react';
import Loader from './Loader';

const Loadable = (child: JSX.Element) => (props: object) => {
    const Component = child;
    return (
        <Suspense fallback={<Loader />}>
            <Component {...props} />
        </Suspense>
    );
}

export default Loadable;

I tried to put a lot of thing in place of JSX.Element but I keep getting these errors:

Argument of type 'LazyExoticComponent<() => Element>' is not assignable to parameter of type 'Element'.
const WrappedComponent = Loadable(lazy(() => import('path/to/my/component')));

and

JSX element type 'Component' does not have any construct or call signatures.
<Component {...props} />

What do I have to put in place of JSX.Element to make it work?
Thanks in advance


Solution

  • const Loadable = (child: JSX.Element) => (props: object) => {
    

    JSX.Element is not the right type for the child argument. A React component is a function (or class) that takes a props object and returns a JSX.Element (or null). So JSX.Element describes the return type of the component rather than the component itself.

    The type for a React component is ComponentType<P> where P is the type for the props.

    We are dealing with React.lazy imports so we need another is another layer of wrapping on the types. Your child argument is not a component but rather it is a lazy import of a component.

    The type for a lazy component is LazyExoticComponent<T extends ComponentType<any>> where T is the type of the component. If you have props P you would use LazyExoticComponent<ComponentType<P>>.

    You don't really know the type P of the component's props because it is different for each component that you wrap. You are currently using a vague type object but that won't give you good type support.

    We can use a generic type P to describe the props of the inner/child component. That way the WrappedComponent will have the correct props type based on the type of the component that it is wrapping.

    That brings us to:

    const Loadable = <P extends object>(
        child: React.LazyExoticComponent<React.ComponentType<P>>
    ) => (props: P) => {
    

    Which is pretty close, but it will give you an error about refs:

    Type 'unknown' is not assignable to type 'PropsWithRef<P & { children?: ReactNode; }>'.
    

    Your higher-order component does not forward refs therefore we need to specify in the types that the returned WrappedComponent does not accept a ref prop. We can do that with another built-in React type PropsWithoutRef.

    const Loadable = <P extends object>(
        child: React.LazyExoticComponent<React.ComponentType<P>>
    ) => (props: React.PropsWithoutRef<P>) => {
    

    You can import the types instead of using React.TypeName. That looks like:

    import { lazy, Suspense, LazyExoticComponent, PropsWithoutRef, ComponentType } from 'react';
    
    const Loadable = <P extends object>(
        child: LazyExoticComponent<ComponentType<P>>
    ) => (props: PropsWithoutRef<P>) => {