(repro at https://github.com/codingismy11to7/lazy-component-typing)
I'm guessing this is an issue with @types/react
and the LazyExoticComponent
definition...
When using React.lazy()
on a component with a type parameter, Typescript can no longer compile
the project.
Given the component:
import React from "react";
export interface Props<T> {
value: T;
valueCallback: (t: T) => void;
}
export default function TypedComponent<T>(props: Props<T>) {
return (
<>
<div>{`${props.value}`}</div>
<button onClick={() => props.valueCallback(props.value)}>click</button>
</>
)
}
This compiles:
import React, {useState} from "react";
import TypedComponent from "./TypedComponent";
export default function StrictComponent() {
const [state, setState] = useState("blah");
return (
<TypedComponent value={state} valueCallback={setState}/>
);
}
while this does not:
import React, {lazy, useState} from "react";
const TypedComponent = lazy(() => import("./TypedComponent"));
export default function LazyComponent() {
const [state, setState] = useState("blah");
return (
<TypedComponent value={state} valueCallback={setState}/>
);
}
TypeScript error in D:/dev/lazy-component-typing/src/LazyComponent.tsx(9,35):
Type 'Dispatch<SetStateAction<string>>' is not assignable to type '(t: unknown) => void'.
Types of parameters 'value' and 't' are incompatible.
Type 'unknown' is not assignable to type 'SetStateAction<string>'.
Type 'unknown' is not assignable to type '(prevState: string) => string'. TS2322
7 |
8 | return (
> 9 | <TypedComponent value={state} valueCallback={setState}/>
| ^
10 | );
11 | }
12 |
The React. lazy function lets you render a dynamic import as a regular component.
The React. lazy() function allows you to render a dynamic import as a normal component. It makes it simple to construct components that are loaded dynamically yet rendered as regular components. When the component is rendered, the bundle containing it is automatically loaded.
Even though React. lazy is supposed to be used with dynamic imports, it supports just about any Promise that resolves to a react component. This fits perfectly with our needs.
TL;DR:
You can override React.lazy
type definition to support generics.
react.d.ts:
declare namespace React {
function lazy<T extends ComponentType<any>>(
factory: () => Promise<{ default: T }>,
): T;
}
Explanation: Original React.lazy
declaration looks like this:
function lazy<T extends ComponentType<any>>(
factory: () => Promise<{ default: T }>
): LazyExoticComponent<T>;
It returns LazyExoticComponent
type, which is superset of ExoticComponent
. Unfortunately, this interface is written in way which don't support generics. However, according to comment above the interface, it is meant only for internal distinction between "regular" and other components, like results of lazy
and memo
calls; in JSX syntax there's no difference between this ExoticComponent
and Component
. That's why we can safely omit LaxyExoticComponent
type in our custom override and make use of generics.
Not the ideal solution, but I was able to achieve this by exporting the component's type, and then using that to cast the result of React.lazy
export type TypedComponentType = typeof TypedComponent;
And then
import type { TypeComponent } from './TypedComponent';
const TypedComponent = lazy(() => import("./TypedComponent")) as TypedComponentType;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With