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/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} + + +
+
+ ); +}; 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