Skip to content

Commit 19e8452

Browse files
committed
[IMPL] Lazy Component
1 parent 29811f2 commit 19e8452

8 files changed

Lines changed: 68 additions & 20 deletions

File tree

apps/react-tools-demo/src/markdown/Lazy.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
# Lazy
22
Component Wrapper to lazy loading a Component. [See demo](https://nDriaDev.io/react-tools/#/components/Lazy)
33

4+
## Usage
5+
6+
```tsx
7+
export default function LC() {
8+
return (
9+
<Lazy
10+
factory={() => import('./LazyComponent').then(res => new Promise<{ [k: string]: ComponentType<unknown> }>(resolve => setTimeout(()=>resolve(res), 5000)))}
11+
fallback={<p>Loading component...</p>}
12+
/>
13+
);
14+
}
15+
```
16+
17+
> The component uses _Lazy_ component to lazy load a component imported dynamically by _factory_ prop. The component loading is delayed by 5 seconds. During this time, _fallback_ prop is shown that renders a p element with the text __Loading component...__.
18+
19+
420
## API
521

622
```tsx
7-
Lazy<T extends ComponentType<unknown>>({ factory, componentName, fallback, beforeLoad, afterLoad }: { factory: () => Promise<{ [K:string]: T }>, componentName?: string, fallback?: ReactNode, beforeLoad?: ()=>void, afterLoad?: ()=>void })
23+
Lazy<T extends { default: ComponentType<unknown> } | { [k: string]: ComponentType<unknown> }>({ factory, componentName, fallback, beforeLoad, afterLoad }: { factory: () => Promise<T>, componentName?: string, fallback?: ReactNode, beforeLoad?: ()=>void, afterLoad?: ()=>void })
824
```
925

1026
> ### Params

apps/react-tools-demo/src/pages/components/lazy/Lazy.md

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Lazy } from "../../../../../../packages/react-tools/src";
2+
3+
/**
4+
The component uses _Lazy_ component to lazy load a component imported dynamically by _factory_ prop. The component loading is delayed by 5 seconds. During this time, _fallback_ prop is shown that renders a p element with the text __Loading component...__.
5+
*/
6+
export default function LC() {
7+
return (
8+
<Lazy
9+
factory={() => import('./LazyComponent').then(async res => {
10+
await new Promise(resolve => setTimeout(resolve, 5000));
11+
return res;
12+
})}
13+
fallback={<p>Loading component...</p>}
14+
/>
15+
);
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useEffect, useState } from "react";
2+
3+
export const LazyComponent = () => {
4+
const [timer, setTimer] = useState(new Date().toLocaleTimeString());
5+
6+
useEffect(() => {
7+
const id = setInterval(() => setTimer(new Date().toLocaleTimeString()), 1000);
8+
return () => clearInterval(id);
9+
}, []);
10+
return (<>
11+
<h3>Lazy Component</h3>
12+
<p>Clock: {timer}</p>
13+
<p>This component has been loaded lazy.</p>
14+
</>);
15+
}

apps/react-tools-demo/src/pages/home/Home.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default function Home() {
66
<div className='home-container'>
77
<img src={Logo} alt="react" className="logo"/>
88
<h1 className='title'>React Tools</h1>
9-
<h2>A collection of hooks and utilities for React</h2>
9+
<h2>A collection of hooks, components and utilities for React</h2>
1010
<div className='npm-container'>
1111
<span
1212
className='npm-copy'
@@ -32,7 +32,7 @@ export default function Home() {
3232
</div>
3333
<div className='cell'>
3434
<div className='title'>Demos</div>
35-
<div className='body'>All hooks have a demo that allow you to try them.</div>
35+
<div className='body'>All implementations have a demo that allow you to try them.</div>
3636
</div>
3737
</div>
3838
</div>

apps/react-tools-demo/src/router/Router.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ import useVisibleMD from "../markdown/useVisible.md?raw"
132132
import useWebSocketMD from "../markdown/useWebSocket.md?raw"
133133
import useWebWorkerMD from "../markdown/useWebWorker.md?raw"
134134
import useWebWorkerFnMD from "../markdown/useWebWorkerFn.md?raw"
135+
const Lazy = lazy((() => import('../pages/components/lazy/Lazy').then(module => ({default: "default" in module ? module["default"] : module["Lazy"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
135136
const Show = lazy((() => import('../pages/components/show/Show').then(module => ({default: "default" in module ? module["default"] : module["Show"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
136137
import HomeWrapper from '../pages/home/HomeWrapper'
137138
const UseActiveElement = lazy((() => import('../pages/hooks/api-dom/useActiveElement/UseActiveElement').then(module => ({default: "default" in module ? module["default"] : module["UseActiveElement"]}))) as unknown as () => Promise<{ default: ComponentType; }>)
@@ -982,7 +983,7 @@ function Router() {
982983
{
983984
path: "Lazy",
984985
element: <Suspense fallback={<Spinner/>}>
985-
<ComponentLayout markdown={LazyMD} />
986+
<ComponentLayout markdown={LazyMD} component={<Lazy/>}/>
986987
</Suspense>
987988
},
988989
{
@@ -1090,7 +1091,7 @@ function Router() {
10901091
{
10911092
path: "lazy",
10921093
element: <Suspense fallback={<Spinner/>}>
1093-
<ComponentLayout markdown={lazyMD} />
1094+
<ComponentLayout markdown={lazyMD} component={<Lazy/>}/>
10941095
</Suspense>
10951096
},
10961097
{

packages/react-tools/src/components/Lazy.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,40 @@
11
import { Suspense, JSX, ComponentType, ReactNode } from "react";
22

3-
const promise: { factorySerialized: string; promise: Promise<void>; response?: () => JSX.Element, reject?: unknown }[] = [];
3+
const promise: { factorySerialized: string; promise: Promise<void>; response?: ComponentType<unknown>, error?: unknown }[] = [];
44
const DynamicComponent = ({ factory, componentName, beforeLoad, afterLoad }: { factory: () => Promise<{[k:string]:unknown}>, componentName?:string, beforeLoad?: () => void, afterLoad?: () => void }) => {
55
const fSerialized = factory.toString();
66
for (const prom of promise) {
77
if (prom.factorySerialized === fSerialized) {
88
if (prom.response) {
99
!!afterLoad && afterLoad();
10-
return prom.response && prom.response();
10+
return prom.response && (prom as unknown as {response: () => JSX.Element}).response();
1111
}
12-
if (prom.reject) {
13-
throw prom.reject;
12+
if (prom.error) {
13+
throw prom.error;
1414
}
1515
throw prom.promise;
1616
}
1717
}
1818

1919
const current: typeof promise[number] = {
20-
factorySerialized: fSerialized,
20+
factorySerialized: String.raw`${fSerialized}`,
2121
promise: (() => {
2222
!!beforeLoad && beforeLoad();
2323
return factory()
2424
.then((res) => {
2525
if ("default" in res) {
26-
return { default: res.default };
26+
current.response = res.default as ComponentType<unknown>;
2727
}
2828
if (!!componentName && componentName in res) {
29-
return { default: res[componentName] };
29+
current.response = res[componentName] as ComponentType<unknown>;
3030
}
3131
const keys = Reflect.ownKeys(res).filter(el => el !== "__esModule");
3232
if (keys.length > 0) {
33-
return { default: res[keys[0] as keyof typeof res] };
33+
current.response = res[keys[0] as keyof typeof res] as ComponentType<unknown>;
3434
}
3535
throw Error("Invalid import");
3636
})
37-
.catch(err => current.reject = err);
37+
.catch(err => current.error = err);
3838
})(),
3939
}
4040
promise.push(current);
@@ -43,15 +43,15 @@ const DynamicComponent = ({ factory, componentName, beforeLoad, afterLoad }: { f
4343

4444
/**
4545
* **`Lazy`**: Component Wrapper to lazy loading a Component. [See demo](https://nDriaDev.io/react-tools/#/components/Lazy)
46-
* @param {Object} param - properties to load component.
46+
* @param {Object} param - properties to load component.
4747
* @param {() => Promise<{ [k:string]: T }>} param.factory - function that returns a Promise or another thenable.
4848
* @param {string} [param.componentName] - name of the of the module to load lazy. If it is missing, and the _load_ execution result not have a default property, the first key in res is returned as result.
4949
* @param {ReactNode} [object.fallback] - optional element to render when _when_ prop is false.
50-
* @param {()=>void} [param.beforeLoad] - function that will be executed before loading component .
51-
* @param {()=>void} [param.afterLoad] - function that will be executed after loading component .
50+
* @param {()=>void} [param.beforeLoad] - function that will be executed before loading component .
51+
* @param {()=>void} [param.afterLoad] - function that will be executed after loading component .
5252
* @returns {JSX.Element} element
5353
*/
54-
export const Lazy = <T extends ComponentType<unknown>>({ factory, componentName, fallback, beforeLoad, afterLoad }: { factory: () => Promise<{ [K:string]: T }>, componentName?: string, fallback?: ReactNode, beforeLoad?: ()=>void, afterLoad?: ()=>void }) => {
54+
export const Lazy = <T extends { default: ComponentType<unknown> } | { [k: string]: ComponentType<unknown> }>({ factory, componentName, fallback, beforeLoad, afterLoad }: { factory: () => Promise<T>, componentName?: string, fallback?: ReactNode, beforeLoad?: ()=>void, afterLoad?: ()=>void }) => {
5555
return <Suspense fallback={fallback}>
5656
<DynamicComponent factory={factory} componentName={componentName} beforeLoad={beforeLoad} afterLoad={afterLoad} />
5757
</Suspense>

packages/react-tools/src/hooks/api-dom/usePromiseSuspensible.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const usePromiseSuspensible = <T extends (...args: unknown[]) => Promise<
1818
})
1919
for (const cached of promiseCache) {
2020
index.current = index.current+1;
21-
if (isDeepEqual([...deps, promise.toString()], cached.deps)) {
21+
if (isDeepEqual([...deps, String.raw`${promise.toString()}`], cached.deps)) {
2222
if ("error" in cached) {
2323
throw cached.error;
2424
}
@@ -29,7 +29,7 @@ export const usePromiseSuspensible = <T extends (...args: unknown[]) => Promise<
2929
}
3030
}
3131
const cached: { deps: DependencyList, promise: Promise<void>, error?: unknown, response?: Awaited<ReturnType<T>> } = {
32-
deps:[...deps, promise.toString()],
32+
deps:[...deps, String.raw`${promise.toString()}`],
3333
promise: promise()
3434
.then(response => {
3535
cached.response = response as Awaited<ReturnType<T>>;

0 commit comments

Comments
 (0)