https://github.com/alexfigliolia/react-lazy
Lazy components that dynamically load on mount and support preloading
https://github.com/alexfigliolia/react-lazy
code-splitting lazy-loading performance react
Last synced: about 1 month ago
JSON representation
Lazy components that dynamically load on mount and support preloading
- Host: GitHub
- URL: https://github.com/alexfigliolia/react-lazy
- Owner: alexfigliolia
- Created: 2024-06-12T17:28:43.000Z (12 months ago)
- Default Branch: main
- Last Pushed: 2025-01-13T19:25:59.000Z (4 months ago)
- Last Synced: 2025-03-30T22:32:51.438Z (about 2 months ago)
- Topics: code-splitting, lazy-loading, performance, react
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/@figliolia/react-lazy
- Size: 254 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# React Lazy
Lazy components that support preloading and dynamically load on mount.## Installation
```bash
npm i -S @figliolia/react-lazy
#or
yarn add @figliolia/react-lazy
```## Basic Usage
```tsx
import type { ErrorInfo } from "react";
import { CreateLazyComponent } from "@figliolia/react-lazy";const MyLazyComponent = CreateLazyComponent({
fallback:Loading,
errorBoundary:Whoops!,
loader: () => import("./path/to/Component"),
onError: (error: Error, errorInfo: ErrorInfo) => {}
});// Optionally Preload your component using:
void MyLazyComponent.preload()// Render your component without preloading and it'll
// load on mount
export const MyApp () => {
return (
{/* your other component markup */}
);
}
```## CreateLazyComponent()
### Parameters
`loader`: An async function that resolves to `{ default: ComponentType }`. This function is passed to `React.lazy()`.`fallback`: (optional) A `ReactNode` to display while to the lazy component is suspended
`errorBoundary`: (optional) A `ReactNode` to display if the lazy component throws an error
`onError`: (optional) A callback to execute if the lazy component throws an error.
### Returns
A `LazyComponent` instance## Advanced Usage - Preloading
This feature is an additional optimization that allows you to optimistically load dynamic components ahead of when they're actually needed.Consider a case where you have a user logging into your application for the first time. When they press your login button, you'll probably send a request to your server to validate the user's credentials - then, if they are valid, you'll redirect the user into your app.
While the credentials are being sent to the server, it may also be prudent to *securely* `preload` some of the components that are sitting behind your authentication. A working example may look something like the following:
```tsx
import { useRef, useCallback } from "react";
import { LazyHeader, LazyContent, LazyFooter } from "./your-app-components";export const LoginScreen = () => {
const preloaded = useRef(false);const preloadComponents = useCallback(async () => {
if(preloaded.current) {
return;
}
try {
await Promise.all([
LazyHeader.preload(),
LazyContent.preload(),
LazyFooter.preload(),
]);
preloaded.current = true;
} catch() {
// silence
}
}, [preloaded])const onSubmit = useCallback(async (e) => {
e.preventDefault();
void preloadComponents();
try {
await fetch("/api/auth", {
method: "POST",
body: JSON.stringify(/* Login Credentials */)
});
redirect("to/your/app");
} catch(e) {
// handle error
}
}, [preloadComponents]);return (
{/* login inputs */}
);
}
```
Using this technique, we can utilize the time that an API request is already in-flight to cache component assets in the browser. This way when authentication completes the redirect to our main application content is instantaneous.## Advanced Usage - Priority Queue
For very large applications, it's expected that many components will be lazy loaded. For instances such as these, it may make sense to yield the main thread back to the end user and pause loading JavaScript temporarily.This pattern has been in use at companies like Facebook for several years. Check out [Facebook's blog post](https://engineering.fb.com/2019/04/22/developer-tools/isinputpending-api/) on how they handle large numbers of asynchronous tasks.
This library comes with a solution for cases such as these:
```tsx
import { PriorityQueue, LazyComponentFactory } from "@figliolia/react-lazy"export const LoadingQueue = new PriorityQueue(
5 /* milliseconds to yield to the main thread */
);export const CreateLazyComponent = LazyComponentFactory(LoadingQueue);
```The `LoadingQueue` in the example above is a priority queue that'll handle loading lazy components created using `CreateLazyComponent()`. The `LoadingQueue` will detect when a user is attempting to interact with the page and pause loading for a specified number of milliseconds to allow events to dispatch on the main thread.
In addition to user-input awareness, the `CreateLazyComponent` function generated by `LazyComponentFactory` now comes with the ability to prioritize loading certain components against others using a `PriorityLevel`:
```tsx
import { PriorityQueue, LazyComponentFactory, PriorityLevel } from "@figliolia/react-lazy"export const LoadingQueue = new PriorityQueue(
5 /* milliseconds to yield to the main thread */
);
export const CreateLazyComponent = LazyComponentFactory(LoadingQueue);const MyHighPriorityComponent = CreateLazyComponent({
priority: PriorityLevel.Immediate,
loader: () => import("./HighPriorityComponent")
});const MyLowPriorityComponent = CreateLazyComponent({
priority: PriorityLevel.Background,
loader: () => import("./LowPriorityComponent")
});
```By default `Priority.Immediate` components will be loaded ahead of `Priority.Background` components.
A good use of `Priority.Immediate` would be for components that are core to your UI's most basic functionality, such as headers and sidebars. Similarly, for components that are conditionally used such as modals or drawer menus `Priority.Background` is more appropriate.
Utilitizing this pattern in large applications can have a great effect on your core metrics such as `FMP` and `TTI`.
In addition to loading components, the `LoadingQueue` in the example above can also be used for just about any asynchronous task. You can use it to load a large javascript library in the background or even preload images that might be below the fold:
```typescript
import { PriorityQueue, PriorityLevel } from "@figliolia/react-lazy";export const LoadingQueue = new PriorityQueue();
LoadingQueue.enqueue(
PriorityLevel.Background,
() => import("expensive-node-module").then(() => {
// do something with your expensive module when
// it loads
})
)LoadingQueue.enqueue(
PriorityLevel.Background,
() => new Promise(resolve => {
const image = new Image();
image.onload = resolve;
image.onerror = resolve;
image.src = "path/to/large-image.jpeg";
})
)
```Your expensive JS module and large image will now load optimistically, but behind any Components or tasks with `Priority.Immediate`.