From 99c564936bcc2c192dc329b12c0d046fbec7a8f2 Mon Sep 17 00:00:00 2001 From: Arnei Date: Thu, 24 Sep 2026 15:03:47 +0200 Subject: [PATCH 1/2] Don't re-render unchanged table rows after each fetch Keep row objects whose data did not change and memoize TableRow, so polling no longer re-renders the whole table. This commit was AI generated --- package-lock.json | 2 +- package.json | 1 + src/components/shared/Table.tsx | 5 ++++- src/slices/tableSlice.ts | 13 ++++++++++--- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index dbcac2b31a..8eba854ae0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "classnames": "^2.5.1", "date-fns": "^4.4.0", "dompurify": "^3.4.14", + "fast-deep-equal": "^3.1.3", "focus-trap-react": "^12.0.3", "formik": "^2.4.9", "http-proxy-middleware": "^4.2.0", @@ -3345,7 +3346,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { diff --git a/package.json b/package.json index 14ce37ed49..f8aedc7a7b 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "classnames": "^2.5.1", "date-fns": "^4.4.0", "dompurify": "^3.4.14", + "fast-deep-equal": "^3.1.3", "focus-trap-react": "^12.0.3", "formik": "^2.4.9", "http-proxy-middleware": "^4.2.0", diff --git a/src/components/shared/Table.tsx b/src/components/shared/Table.tsx index ee78cf9870..95fe2dbd4c 100644 --- a/src/components/shared/Table.tsx +++ b/src/components/shared/Table.tsx @@ -331,7 +331,7 @@ const TableRows = ({ templateMap }: { templateMap: TemplateMap< return ( <> {rowKeys.map(rowKey => ( - ({ rowKey, templateMap }: { rowKey: string, te ); }; +// Rows are only re-rendered if their data changed, not whenever the table is updated +const MemoizedTableRow = React.memo(TableRow) as typeof TableRow; + // Apply a column template and render corresponding components const ColumnTemplate = ({ row, column, templateMap }: {row: T, column: TableColumn, templateMap: TemplateMap}) => { if (!column.template) { diff --git a/src/slices/tableSlice.ts b/src/slices/tableSlice.ts index f4a348ba00..ad2190c423 100644 --- a/src/slices/tableSlice.ts +++ b/src/slices/tableSlice.ts @@ -1,4 +1,5 @@ -import { EntityState, PayloadAction, SerializedError, createEntityAdapter, createSlice, nanoid } from "@reduxjs/toolkit"; +import { EntityState, PayloadAction, SerializedError, createEntityAdapter, createSlice, nanoid, original } from "@reduxjs/toolkit"; +import isEqual from "fast-deep-equal"; import { aclsTableConfig, TableConfig } from "../configs/tableConfigs/aclsTableConfig"; import { Server } from "./serverSlice"; import { Recording } from "./recordingSlice"; @@ -237,15 +238,21 @@ const tableSlice = createSlice({ // Entity Adapter preparations const rows: Row[] = []; + // Rows as they were before this update + const previousRows = original(state.rows)?.entities ?? {}; action.payload.rows.forEach(row => { const rowId = getRowKey(row); // new stable id // @ts-expect-error: Id will not be number - rows.push({ + const newRow: Row = { ...row, id: rowId, - }); + }; + + // Keep the previous object if the row did not change, so it is not re-rendered + const previousRow = previousRows[rowId]; + rows.push(previousRow && isEqual(previousRow, newRow) ? previousRow : newRow); }); // Replace state with the fetched entities From 34c0d9c36d13e3a33687cbbdeb7a2cec03a21b15 Mon Sep 17 00:00:00 2001 From: Arnei Date: Thu, 24 Sep 2026 15:03:47 +0200 Subject: [PATCH 2/2] Only register the close hotkey of modals while they are open Closed modals, such as the ones in every table row, no longer each add keyboard listeners and hotkey registrations. This commit was AI generated --- src/components/shared/modals/Modal.tsx | 106 +++++++++++++++---------- 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/src/components/shared/modals/Modal.tsx b/src/components/shared/modals/Modal.tsx index 1274de6e39..f916b2ee03 100644 --- a/src/components/shared/modals/Modal.tsx +++ b/src/components/shared/modals/Modal.tsx @@ -37,8 +37,6 @@ export const Modal = forwardRef>( { open = false, closeCallback, header, classId, className, children }, ref, ) => { - const { t } = useTranslation(); - const [isOpen, setOpen] = useState(open); const close = useCallback(() => { if (closeCallback !== undefined && !closeCallback()) { @@ -58,48 +56,74 @@ export const Modal = forwardRef>( [close, isOpen], ); - useHotkeys( - availableHotkeys.general.CLOSE_MODAL.sequence, - close, - { - description: - t(availableHotkeys.general.CLOSE_MODAL.description) ?? undefined, - }, - [ref], - ); - return ReactDOM.createPortal( isOpen && - -
-
-
-
- - - -

- {header} -

-
- - Something went wrong. Please close the modal and try again. -
- }> - {children} - - -
-
, + + {children} + , document.body, ); }); + +const ModalContent = ({ + close, + header, + classId, + className, + children, +}: PropsWithChildren<{ + close: () => void, + header: string, + classId: string, + className?: string, +}>) => { + const { t } = useTranslation(); + + useHotkeys( + availableHotkeys.general.CLOSE_MODAL.sequence, + close, + { + description: + t(availableHotkeys.general.CLOSE_MODAL.description) ?? undefined, + }, + [close], + ); + + return ( + +
+
+
+
+ + + +

+ {header} +

+
+ + Something went wrong. Please close the modal and try again. +
+ }> + {children} + + +
+
+ ); +};