fix render-if-visible-hoc's style calculation performance issue (#5647)
This commit is contained in:
parent
bd0ca0cded
commit
c8c9638e5a
5 changed files with 65 additions and 20 deletions
|
|
@ -11,6 +11,7 @@ type Props = {
|
||||||
classNames?: string;
|
classNames?: string;
|
||||||
placeholderChildren?: ReactNode;
|
placeholderChildren?: ReactNode;
|
||||||
defaultValue?: boolean;
|
defaultValue?: boolean;
|
||||||
|
shouldRecordHeights?: boolean;
|
||||||
useIdletime?: boolean;
|
useIdletime?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -23,6 +24,8 @@ const RenderIfVisible: React.FC<Props> = (props) => {
|
||||||
as = "div",
|
as = "div",
|
||||||
children,
|
children,
|
||||||
classNames = "",
|
classNames = "",
|
||||||
|
shouldRecordHeights = true,
|
||||||
|
//placeholder children
|
||||||
placeholderChildren = null, //placeholder children
|
placeholderChildren = null, //placeholder children
|
||||||
defaultValue = false,
|
defaultValue = false,
|
||||||
useIdletime = false,
|
useIdletime = false,
|
||||||
|
|
@ -64,15 +67,15 @@ const RenderIfVisible: React.FC<Props> = (props) => {
|
||||||
|
|
||||||
//Set height after render
|
//Set height after render
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (intersectionRef.current && isVisible) {
|
if (intersectionRef.current && isVisible && shouldRecordHeights) {
|
||||||
placeholderHeight.current = `${intersectionRef.current.offsetHeight}px`;
|
window.requestIdleCallback(() => {
|
||||||
|
if (intersectionRef.current) placeholderHeight.current = `${intersectionRef.current.offsetHeight}px`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [isVisible, intersectionRef]);
|
}, [isVisible, intersectionRef, shouldRecordHeights]);
|
||||||
|
|
||||||
const child = isVisible ? <>{children}</> : placeholderChildren;
|
const child = isVisible ? <>{children}</> : placeholderChildren;
|
||||||
const style: { width?: string; height?: string } = isVisible
|
const style = isVisible || placeholderChildren ? {} : { height: placeholderHeight.current, width: "100%" };
|
||||||
? {}
|
|
||||||
: { height: placeholderHeight.current, width: "100%" };
|
|
||||||
const className = isVisible || placeholderChildren ? classNames : cn(classNames, "bg-custom-background-80");
|
const className = isVisible || placeholderChildren ? classNames : cn(classNames, "bg-custom-background-80");
|
||||||
|
|
||||||
return React.createElement(as, { ref: intersectionRef, style, className }, child);
|
return React.createElement(as, { ref: intersectionRef, style, className }, child);
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,11 @@ import { IIssueDisplayProperties, TIssue, TIssueMap } from "@plane/types";
|
||||||
import { DropIndicator } from "@plane/ui";
|
import { DropIndicator } from "@plane/ui";
|
||||||
import RenderIfVisible from "@/components/core/render-if-visible-HOC";
|
import RenderIfVisible from "@/components/core/render-if-visible-HOC";
|
||||||
import { IssueBlock } from "@/components/issues/issue-layouts/list";
|
import { IssueBlock } from "@/components/issues/issue-layouts/list";
|
||||||
|
import { ListLoaderItemRow } from "@/components/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail } from "@/hooks/store";
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||||
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// types
|
// types
|
||||||
import { HIGHLIGHT_CLASS, getIssueBlockId, isIssueNew } from "../utils";
|
import { HIGHLIGHT_CLASS, getIssueBlockId, isIssueNew } from "../utils";
|
||||||
import { TRenderQuickActions } from "./list-view-types";
|
import { TRenderQuickActions } from "./list-view-types";
|
||||||
|
|
@ -66,6 +68,8 @@ export const IssueBlockRoot: FC<Props> = observer((props) => {
|
||||||
const [isCurrentBlockDragging, setIsCurrentBlockDragging] = useState(false);
|
const [isCurrentBlockDragging, setIsCurrentBlockDragging] = useState(false);
|
||||||
// ref
|
// ref
|
||||||
const issueBlockRef = useRef<HTMLDivElement | null>(null);
|
const issueBlockRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
// hooks
|
||||||
|
const { isMobile } = usePlatformOS();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { subIssues: subIssuesStore } = useIssueDetail();
|
const { subIssues: subIssuesStore } = useIssueDetail();
|
||||||
|
|
||||||
|
|
@ -125,11 +129,14 @@ export const IssueBlockRoot: FC<Props> = observer((props) => {
|
||||||
<DropIndicator classNames={"absolute top-0 z-[2]"} isVisible={instruction === "DRAG_OVER"} />
|
<DropIndicator classNames={"absolute top-0 z-[2]"} isVisible={instruction === "DRAG_OVER"} />
|
||||||
<RenderIfVisible
|
<RenderIfVisible
|
||||||
key={`${issueId}`}
|
key={`${issueId}`}
|
||||||
defaultHeight="3rem"
|
|
||||||
root={containerRef}
|
root={containerRef}
|
||||||
classNames={`relative ${isLastChild && !isExpanded ? "" : "border-b border-b-custom-border-200"}`}
|
classNames={`relative ${isLastChild && !isExpanded ? "" : "border-b border-b-custom-border-200"}`}
|
||||||
verticalOffset={100}
|
verticalOffset={100}
|
||||||
defaultValue={shouldRenderByDefault || isIssueNew(issuesMap[issueId])}
|
defaultValue={shouldRenderByDefault || isIssueNew(issuesMap[issueId])}
|
||||||
|
placeholderChildren={
|
||||||
|
<ListLoaderItemRow shouldAnimate={false} renderForPlaceHolder={true} defaultPropertyCount={4} />
|
||||||
|
}
|
||||||
|
shouldRecordHeights={isMobile}
|
||||||
>
|
>
|
||||||
<IssueBlock
|
<IssueBlock
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
|
|
|
||||||
|
|
@ -76,16 +76,20 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
|
||||||
{/* first column/ issue name and key column */}
|
{/* first column/ issue name and key column */}
|
||||||
<RenderIfVisible
|
<RenderIfVisible
|
||||||
as="tr"
|
as="tr"
|
||||||
defaultHeight="calc(2.75rem - 1px)"
|
|
||||||
root={containerRef}
|
root={containerRef}
|
||||||
placeholderChildren={
|
placeholderChildren={
|
||||||
<td colSpan={100} className="border-[0.5px] border-transparent border-b-custom-border-200" />
|
<td
|
||||||
|
colSpan={100}
|
||||||
|
className="border-[0.5px] border-transparent border-b-custom-border-200"
|
||||||
|
style={{ height: "calc(2.75rem - 1px)" }}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
classNames={cn("bg-custom-background-100 transition-[background-color]", {
|
classNames={cn("bg-custom-background-100 transition-[background-color]", {
|
||||||
"group selected-issue-row": isIssueSelected,
|
"group selected-issue-row": isIssueSelected,
|
||||||
"border-[0.5px] border-custom-border-400": isIssueActive,
|
"border-[0.5px] border-custom-border-400": isIssueActive,
|
||||||
})}
|
})}
|
||||||
verticalOffset={100}
|
verticalOffset={100}
|
||||||
|
shouldRecordHeights={false}
|
||||||
>
|
>
|
||||||
<IssueRowDetails
|
<IssueRowDetails
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,51 @@
|
||||||
import { Fragment, forwardRef } from "react";
|
import { Fragment, forwardRef } from "react";
|
||||||
|
import { cn } from "@plane/editor";
|
||||||
import { Row } from "@plane/ui";
|
import { Row } from "@plane/ui";
|
||||||
import { getRandomInt, getRandomLength } from "../utils";
|
import { getRandomInt, getRandomLength } from "../utils";
|
||||||
|
|
||||||
export const ListLoaderItemRow = forwardRef<HTMLDivElement>((props, ref) => (
|
export const ListLoaderItemRow = forwardRef<
|
||||||
<Row ref={ref} className="flex items-center justify-between h-11 py-3 border-b border-custom-border-200">
|
HTMLDivElement,
|
||||||
|
{ shouldAnimate?: boolean; renderForPlaceHolder?: boolean; defaultPropertyCount?: number }
|
||||||
|
>(({ shouldAnimate = true, renderForPlaceHolder = false, defaultPropertyCount = 6 }, ref) => (
|
||||||
|
<Row
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex items-center justify-between h-11 py-3 ", {
|
||||||
|
"bg-custom-background-100": renderForPlaceHolder,
|
||||||
|
"border-b border-custom-border-200": !renderForPlaceHolder,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="h-5 w-10 bg-custom-background-80 rounded animate-pulse" />
|
<span
|
||||||
<span className={`h-5 w-${getRandomLength(["32", "52", "72"])} bg-custom-background-80 rounded animate-pulse`} />
|
className={cn("h-5 w-10 bg-custom-background-80 rounded", {
|
||||||
|
"animate-pulse": shouldAnimate,
|
||||||
|
"bg-custom-background-90": renderForPlaceHolder,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={cn(`h-5 w-${getRandomLength(["32", "52", "72"])} bg-custom-background-80 rounded`, {
|
||||||
|
"animate-pulse": shouldAnimate,
|
||||||
|
"bg-custom-background-90": renderForPlaceHolder,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{[...Array(6)].map((_, index) => (
|
{[...Array(defaultPropertyCount)].map((_, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{getRandomInt(1, 2) % 2 === 0 ? (
|
{getRandomInt(1, 2) % 2 === 0 ? (
|
||||||
<span key={index} className="h-5 w-5 bg-custom-background-80 rounded animate-pulse" />
|
<span
|
||||||
|
key={index}
|
||||||
|
className={cn("h-5 w-5 bg-custom-background-80 rounded", {
|
||||||
|
"animate-pulse": shouldAnimate,
|
||||||
|
"bg-custom-background-90": renderForPlaceHolder,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className="h-5 w-16 bg-custom-background-80 rounded animate-pulse" />
|
<span
|
||||||
|
className={cn("h-5 w-16 bg-custom-background-80 rounded", {
|
||||||
|
"animate-pulse": shouldAnimate,
|
||||||
|
"bg-custom-background-90": renderForPlaceHolder,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ export class IssueStore implements IIssueStore {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
updateIssue = (issueId: string, issue: Partial<TIssue>) => {
|
updateIssue = (issueId: string, issue: Partial<TIssue>) => {
|
||||||
if (!issue || !issueId || isEmpty(this.issuesMap) || !this.issuesMap[issueId]) return;
|
if (!issue || !issueId || !this.issuesMap[issueId]) return;
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
set(this.issuesMap, [issueId, "updated_at"], getCurrentDateTimeInISO());
|
set(this.issuesMap, [issueId, "updated_at"], getCurrentDateTimeInISO());
|
||||||
Object.keys(issue).forEach((key) => {
|
Object.keys(issue).forEach((key) => {
|
||||||
|
|
@ -92,7 +92,7 @@ export class IssueStore implements IIssueStore {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
removeIssue = (issueId: string) => {
|
removeIssue = (issueId: string) => {
|
||||||
if (!issueId || isEmpty(this.issuesMap) || !this.issuesMap[issueId]) return;
|
if (!issueId || !this.issuesMap[issueId]) return;
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
delete this.issuesMap[issueId];
|
delete this.issuesMap[issueId];
|
||||||
});
|
});
|
||||||
|
|
@ -105,7 +105,7 @@ export class IssueStore implements IIssueStore {
|
||||||
* @returns {TIssue | undefined}
|
* @returns {TIssue | undefined}
|
||||||
*/
|
*/
|
||||||
getIssueById = computedFn((issueId: string) => {
|
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];
|
return this.issuesMap[issueId];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -116,7 +116,7 @@ export class IssueStore implements IIssueStore {
|
||||||
* @returns {Record<string, TIssue> | undefined}
|
* @returns {Record<string, TIssue> | undefined}
|
||||||
*/
|
*/
|
||||||
getIssuesByIds = computedFn((issueIds: string[], type: "archived" | "un-archived") => {
|
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[] = [];
|
const filteredIssues: TIssue[] = [];
|
||||||
Object.values(issueIds).forEach((issueId) => {
|
Object.values(issueIds).forEach((issueId) => {
|
||||||
// if type is archived then check archived_at is not null
|
// if type is archived then check archived_at is not null
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue