When trying to implement a preload()
method for React.lazy()
components, a typical pattern looks something like,
const ReactLazyPreload = (importStatement) => {
const Component = React.lazy(importStatement);
Component.preload = importStatement; // Property 'preload' does not exist on type 'LazyExoticComponent<T>'.
return Component;
};
which can later be used, eg,
const MyComponent = ReactLazyPreload(() => import("./MyComponent.tsx");
const onHover = () => { MyComponent.preload() };
However, the assignment on the 3rd line of the first snippet causes a TS error,
Property 'preload' does not exist on type 'LazyExoticComponent<T>'.
I've been playing around with declare
, but have been unsuccessful in removing the error. What type should be used for the preload()
method?
CodePudding user response:
TypeScript is trying to prevent you from making a mistake here.
Just because other people follow a convention doesn't make it a good one: in this case, it's not a safe one. As a general rule, it's never safe to mutate things you don't own.
While I can't find anything in the React codebase at the current version tag (17.0.2
) that would seem to cause an issue with assigning something to the preload
property of a lazy component, that doesn't mean that the React maintainers won't use this property in a subsequent release. If that happens, and you overwrite that property, then unpredictable behavior would arise.
Instead of mutating the component, just return the preload function alongside it:
import {default as React, lazy} from 'react';
import type {ComponentType, LazyExoticComponent} from 'react';
export type ReactLazyFactory<T = any> = () => Promise<{default: ComponentType<T>}>;
export type ComponentPreloadTuple<T = any> = [
component: LazyExoticComponent<ComponentType<T>>,
preloadFn: () => void,
];
export function getLazyComponentWithPreload <T = any>(componentPath: string): ComponentPreloadTuple<T>;
export function getLazyComponentWithPreload <T = any>(factory: ReactLazyFactory<T>): ComponentPreloadTuple<T>;
export function getLazyComponentWithPreload <T = any>(input: string | ReactLazyFactory<T>): ComponentPreloadTuple<T> {
const factory = () => typeof input === 'string' ? import(input) : input();
return [lazy(factory), factory];
}
// ----------
// Example.tsx
export type ExampleProps = {
text: string;
};
export default function ExampleComponent ({text}: ExampleProps) {
return <div>{text}</div>;
}
// ----------
// AnotherComponent.tsx
// use with path to component:
const [Example1, preloadExample1] = getLazyComponentWithPreload<ExampleProps>('./Example');
// use with factory function:
const [Example2, preloadExample2] = getLazyComponentWithPreload<ExampleProps>(() => import('./Example'));
CodePudding user response:
// extend lazy component with `preload` property
interface LazyPreload<Props>
extends React.LazyExoticComponent<React.ComponentType<Props>> {
preload: () => {};
}
function ReactLazyPreload<Props>(
importStatement: () => Promise<{ default: React.ComponentType<Props> }>
) {
// use Object.assign to set preload
// otherwise it will complain that Component doesn't have preload
const Component: LazyPreload<Props> = Object.assign(
React.lazy(importStatement),
{
preload: importStatement,
}
);
return Component;
}