From c8c9638e5a34800d6d5ec1a9c967a3d680253932 Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:02:46 +0530 Subject: [PATCH] fix render-if-visible-hoc's style calculation performance issue (#5647) --- .../components/core/render-if-visible-HOC.tsx | 15 ++++--- .../issues/issue-layouts/list/block-root.tsx | 9 +++- .../issue-layouts/spreadsheet/issue-row.tsx | 8 +++- .../ui/loader/layouts/list-layout-loader.tsx | 45 ++++++++++++++++--- web/core/store/issue/issue.store.ts | 8 ++-- 5 files changed, 65 insertions(+), 20 deletions(-) diff --git a/web/core/components/core/render-if-visible-HOC.tsx b/web/core/components/core/render-if-visible-HOC.tsx index 226e6c235d5..0b30fb22bf4 100644 --- a/web/core/components/core/render-if-visible-HOC.tsx +++ b/web/core/components/core/render-if-visible-HOC.tsx @@ -11,6 +11,7 @@ type Props = { classNames?: string; placeholderChildren?: ReactNode; defaultValue?: boolean; + shouldRecordHeights?: boolean; useIdletime?: boolean; }; @@ -23,6 +24,8 @@ const RenderIfVisible: React.FC = (props) => { as = "div", children, classNames = "", + shouldRecordHeights = true, + //placeholder children placeholderChildren = null, //placeholder children defaultValue = false, useIdletime = false, @@ -64,15 +67,15 @@ const RenderIfVisible: React.FC = (props) => { //Set height after render useEffect(() => { - if (intersectionRef.current && isVisible) { - placeholderHeight.current = `${intersectionRef.current.offsetHeight}px`; + if (intersectionRef.current && isVisible && shouldRecordHeights) { + window.requestIdleCallback(() => { + if (intersectionRef.current) placeholderHeight.current = `${intersectionRef.current.offsetHeight}px`; + }); } - }, [isVisible, intersectionRef]); + }, [isVisible, intersectionRef, shouldRecordHeights]); const child = isVisible ? <>{children} : placeholderChildren; - const style: { width?: string; height?: string } = isVisible - ? {} - : { height: placeholderHeight.current, width: "100%" }; + const style = isVisible || placeholderChildren ? {} : { height: placeholderHeight.current, width: "100%" }; const className = isVisible || placeholderChildren ? classNames : cn(classNames, "bg-custom-background-80"); return React.createElement(as, { ref: intersectionRef, style, className }, child); diff --git a/web/core/components/issues/issue-layouts/list/block-root.tsx b/web/core/components/issues/issue-layouts/list/block-root.tsx index 6401fcb5e37..c984dc8f3c6 100644 --- a/web/core/components/issues/issue-layouts/list/block-root.tsx +++ b/web/core/components/issues/issue-layouts/list/block-root.tsx @@ -13,9 +13,11 @@ import { IIssueDisplayProperties, TIssue, TIssueMap } from "@plane/types"; import { DropIndicator } from "@plane/ui"; import RenderIfVisible from "@/components/core/render-if-visible-HOC"; import { IssueBlock } from "@/components/issues/issue-layouts/list"; +import { ListLoaderItemRow } from "@/components/ui"; // hooks import { useIssueDetail } from "@/hooks/store"; import { TSelectionHelper } from "@/hooks/use-multiple-select"; +import { usePlatformOS } from "@/hooks/use-platform-os"; // types import { HIGHLIGHT_CLASS, getIssueBlockId, isIssueNew } from "../utils"; import { TRenderQuickActions } from "./list-view-types"; @@ -66,6 +68,8 @@ export const IssueBlockRoot: FC = observer((props) => { const [isCurrentBlockDragging, setIsCurrentBlockDragging] = useState(false); // ref const issueBlockRef = useRef(null); + // hooks + const { isMobile } = usePlatformOS(); // store hooks const { subIssues: subIssuesStore } = useIssueDetail(); @@ -125,11 +129,14 @@ export const IssueBlockRoot: FC = observer((props) => { + } + shouldRecordHeights={isMobile} > { {/* first column/ issue name and key column */} + } classNames={cn("bg-custom-background-100 transition-[background-color]", { "group selected-issue-row": isIssueSelected, "border-[0.5px] border-custom-border-400": isIssueActive, })} verticalOffset={100} + shouldRecordHeights={false} > ((props, ref) => ( - +export const ListLoaderItemRow = forwardRef< + HTMLDivElement, + { shouldAnimate?: boolean; renderForPlaceHolder?: boolean; defaultPropertyCount?: number } +>(({ shouldAnimate = true, renderForPlaceHolder = false, defaultPropertyCount = 6 }, ref) => ( +
- - + +
- {[...Array(6)].map((_, index) => ( + {[...Array(defaultPropertyCount)].map((_, index) => ( {getRandomInt(1, 2) % 2 === 0 ? ( - + ) : ( - + )} ))} diff --git a/web/core/store/issue/issue.store.ts b/web/core/store/issue/issue.store.ts index ed4ecc4d576..95ca24f6692 100644 --- a/web/core/store/issue/issue.store.ts +++ b/web/core/store/issue/issue.store.ts @@ -77,7 +77,7 @@ export class IssueStore implements IIssueStore { * @returns {void} */ updateIssue = (issueId: string, issue: Partial) => { - if (!issue || !issueId || isEmpty(this.issuesMap) || !this.issuesMap[issueId]) return; + if (!issue || !issueId || !this.issuesMap[issueId]) return; runInAction(() => { set(this.issuesMap, [issueId, "updated_at"], getCurrentDateTimeInISO()); Object.keys(issue).forEach((key) => { @@ -92,7 +92,7 @@ export class IssueStore implements IIssueStore { * @returns {void} */ removeIssue = (issueId: string) => { - if (!issueId || isEmpty(this.issuesMap) || !this.issuesMap[issueId]) return; + if (!issueId || !this.issuesMap[issueId]) return; runInAction(() => { delete this.issuesMap[issueId]; }); @@ -105,7 +105,7 @@ export class IssueStore implements IIssueStore { * @returns {TIssue | undefined} */ getIssueById = computedFn((issueId: string) => { - if (!issueId || isEmpty(this.issuesMap) || !this.issuesMap[issueId]) return undefined; + if (!issueId || !this.issuesMap[issueId]) return undefined; return this.issuesMap[issueId]; }); @@ -116,7 +116,7 @@ export class IssueStore implements IIssueStore { * @returns {Record | undefined} */ getIssuesByIds = computedFn((issueIds: string[], type: "archived" | "un-archived") => { - if (!issueIds || issueIds.length <= 0 || isEmpty(this.issuesMap)) return []; + if (!issueIds || issueIds.length <= 0) return []; const filteredIssues: TIssue[] = []; Object.values(issueIds).forEach((issueId) => { // if type is archived then check archived_at is not null