Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you correctly use React.lazy() in Typescript to import a react component with a generic type parameter?

(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 | 
like image 633
codingismy11to7 Avatar asked May 07 '20 20:05

codingismy11to7


People also ask

What does lazy () do in react?

The React. lazy function lets you render a dynamic import as a regular component.

When should I use react lazy?

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.

Can I use react lazy in react native?

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.


2 Answers

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.

like image 188
tymzap Avatar answered Oct 16 '22 10:10

tymzap


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;
like image 2
slinhart Avatar answered Oct 16 '22 10:10

slinhart