PromucFlow_constructor/app/client/src/widgets/withLazyRender.tsx
balajisoundar bacb77a352
chore: Render below the fold widget components when browser is idle using Intersection Observer (#18747)
## Description
In order to improve the first load of the applications, now we're only
rendering the widget components that are [above the
fold](https://en.wikipedia.org/wiki/Above_the_fold#In_web_design) right
away and rendering the other widget components whenever the browser is
idle. This decreases the amount of time it takes before the first paint
on the screen.

This is getting shipped behind a feature flag!
2023-02-08 16:53:39 +05:30

67 lines
2.1 KiB
TypeScript

import { useEffect, useRef, useState } from "react";
import React from "react";
import BaseWidget, { WidgetProps } from "./BaseWidget";
import { REQUEST_IDLE_CALLBACK_TIMEOUT } from "constants/AppConstants";
import { useSelector } from "react-redux";
import { selectFeatureFlags } from "selectors/usersSelectors";
export function withLazyRender(Widget: typeof BaseWidget) {
return function WrappedComponent(props: WidgetProps) {
const features = useSelector(selectFeatureFlags);
const [deferRender, setDeferRender] = useState(
!!features.LAZY_CANVAS_RENDERING,
);
const wrapperRef = useRef<HTMLDivElement>(null);
let idleCallbackId: number;
let observer: IntersectionObserver;
useEffect(() => {
if (wrapperRef.current && deferRender) {
/*
* For the hidden widgets, we are observing till it,
* 1. Scrolls into view, or
* 2. idleCallback is called (browser is either idle or timed out)
* which ever happens first
*/
observer = new IntersectionObserver(
(entries: IntersectionObserverEntry[]) => {
if (!!entries.find((entry) => entry.isIntersecting)) {
setDeferRender(false);
(window as any).cancelIdleCallback(idleCallbackId);
observer.disconnect();
} else if (!idleCallbackId) {
idleCallbackId = (window as any).requestIdleCallback(
() => {
setDeferRender(false);
observer.disconnect();
},
{
timeout: REQUEST_IDLE_CALLBACK_TIMEOUT.lowPriority,
},
);
}
},
{
root: null,
threshold: 0,
},
);
observer.observe(wrapperRef.current);
} else {
setDeferRender(false);
}
return () => {
(window as any).cancelIdleCallback(idleCallbackId);
observer.disconnect();
};
}, []);
return (
<Widget {...props} deferRender={deferRender} wrapperRef={wrapperRef} />
);
};
}