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
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>) => {