fix: prerendering expensive children in accordion

This commit is contained in:
Gareth
2026-01-16 16:41:06 -08:00
parent 3ee7f20eb6
commit d772dce5fa
2 changed files with 99 additions and 25 deletions
@@ -0,0 +1,89 @@
import React, { useEffect, useState } from "react";
import {
AccordionItem,
AccordionItemContent,
AccordionItemTrigger,
AccordionRoot,
} from "../ui/accordion";
import { Text } from "@chakra-ui/react";
export interface HeavyAccordionItem {
key: string;
label: string;
children: React.ReactNode;
}
interface HeavyAccordionProps {
items: HeavyAccordionItem[];
defaultExpanded?: string[];
}
export const HeavyAccordion = ({
items,
defaultExpanded = [],
}: HeavyAccordionProps) => {
const [visitedKeys, setVisitedKeys] = useState<Set<string>>(new Set());
useEffect(() => {
if (defaultExpanded.length > 0) {
setVisitedKeys((prev) => {
let changed = false;
for (const k of defaultExpanded) {
if (!prev.has(k)) {
changed = true;
break;
}
}
if (changed) {
const next = new Set(prev);
defaultExpanded.forEach((k) => next.add(k));
return next;
}
return prev;
});
}
}, [defaultExpanded.join(",")]);
const handleAccordionChange = (e: { value: string[] }) => {
setVisitedKeys((prev) => {
let changed = false;
for (const k of e.value) {
if (!prev.has(k)) {
changed = true;
break;
}
}
if (changed) {
const next = new Set(prev);
e.value.forEach((k) => next.add(k));
return next;
}
return prev;
});
};
return (
<AccordionRoot
collapsible
multiple
defaultValue={defaultExpanded}
variant="plain"
onValueChange={handleAccordionChange}
>
{items.map((item) => (
<AccordionItem key={item.key} value={item.key} border="none">
<AccordionItemTrigger py={2}>
<Text fontSize="sm" fontWeight="medium">
{item.label}
</Text>
</AccordionItemTrigger>
<AccordionItemContent pb={4}>
{visitedKeys.has(item.key) || defaultExpanded.includes(item.key)
? item.children
: null}
</AccordionItemContent>
</AccordionItem>
))}
</AccordionRoot>
);
};
+10 -25
View File
@@ -5,6 +5,7 @@ import {
OperationRestore,
OperationStatus,
} from "../../../gen/ts/v1/operations_pb";
import { HeavyAccordion } from "../../components/common/HeavyAccordion";
import {
Button,
GridItem,
@@ -28,12 +29,7 @@ import { FiFileText, FiMoreVertical, FiTrash2, FiX } from "react-icons/fi";
import { ProgressCircle } from "../../components/ui/progress-circle";
import { ProgressBar, ProgressRoot } from "../../components/ui/progress";
import { toaster } from "../../components/ui/toaster";
import {
AccordionItem,
AccordionItemTrigger,
AccordionItemContent,
AccordionRoot,
} from "../../components/ui/accordion";
import {
BackupProgressEntry,
ResticSnapshot,
@@ -111,6 +107,7 @@ export const OperationRow = ({
const displayType = getTypeForDisplay(operation);
const setRefresh = useState(0)[1];
useEffect(() => {
if (operation.status === OperationStatus.STATUS_INPROGRESS) {
const interval = setInterval(() => {
@@ -282,6 +279,7 @@ export const OperationRow = ({
bodyItems.push({
key: "browser",
label: m.op_row_snapshot_browser(),
children: (
<SnapshotBrowser
snapshotId={snapshotOp.snapshot!.id}
@@ -385,6 +383,8 @@ export const OperationRow = ({
}
}
return (
<Box
className="backrest visible-on-hover"
@@ -436,25 +436,10 @@ export const OperationRow = ({
{bodyItems.length > 0 && (
<Box mt={2} pl={2}>
<AccordionRoot
collapsible
multiple
defaultValue={expandedBodyItems}
variant="plain"
>
{bodyItems.map((item) => (
<AccordionItem key={item.key} value={item.key} border="none">
<AccordionItemTrigger py={2}>
<Text fontSize="sm" fontWeight="medium">
{item.label}
</Text>
</AccordionItemTrigger>
<AccordionItemContent pb={4}>
{item.children}
</AccordionItemContent>
</AccordionItem>
))}
</AccordionRoot>
<HeavyAccordion
items={bodyItems}
defaultExpanded={expandedBodyItems}
/>
</Box>
)}
</Box>