Skip to content

Commit f645567

Browse files
authored
fix(data-modeling): throttle diagram updates to avoid frequent render issues COMPASS-9738 (#7395)
1 parent be5501a commit f645567

File tree

3 files changed

+81
-12
lines changed

3 files changed

+81
-12
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
3+
const DEFAULT_REFRESH_RATE_MS = 250;
4+
5+
export const useThrottledProps = <T extends Record<string, unknown>>(
6+
props: T,
7+
refreshRate: number = DEFAULT_REFRESH_RATE_MS
8+
): T => {
9+
// Throttling mechanism for Component content updates.
10+
const lastUpdateMS = useRef(0);
11+
const pendingUpdate = useRef<NodeJS.Timeout | null>(null);
12+
const [throttledProps, setThrottledProps] = useState<T>(props);
13+
14+
// Throttle props updating. This ensures we don't run
15+
// into broken state bugs with ReactFlow like COMPASS-9738.
16+
useEffect(() => {
17+
const now = Date.now();
18+
const timeSinceLastUpdate = now - lastUpdateMS.current;
19+
20+
const updateProps = () => {
21+
lastUpdateMS.current = Date.now();
22+
setThrottledProps(props);
23+
};
24+
25+
if (timeSinceLastUpdate >= refreshRate) {
26+
updateProps();
27+
} else {
28+
if (pendingUpdate.current) {
29+
clearTimeout(pendingUpdate.current);
30+
}
31+
32+
// Schedule update for the remaining time.
33+
const remainingTime = refreshRate - timeSinceLastUpdate;
34+
pendingUpdate.current = setTimeout(updateProps, remainingTime);
35+
}
36+
37+
return () => {
38+
if (pendingUpdate.current) {
39+
clearTimeout(pendingUpdate.current);
40+
pendingUpdate.current = null;
41+
}
42+
};
43+
}, [props, refreshRate]);
44+
45+
return throttledProps;
46+
};

packages/compass-components/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ export {
152152
Theme,
153153
ThemeProvider,
154154
} from './hooks/use-theme';
155+
export { useThrottledProps } from './hooks/use-throttled-props';
155156
export {
156157
ContentWithFallback,
157158
FadeInPlaceholder,

packages/compass-data-modeling/src/components/diagram-editor.tsx

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
useDarkMode,
3030
useDrawerActions,
3131
useDrawerState,
32+
useThrottledProps,
3233
rafraf,
3334
} from '@mongodb-js/compass-components';
3435
import { cancelAnalysis, retryAnalysis } from '../store/analysis-process';
@@ -324,6 +325,37 @@ const DiagramContent: React.FunctionComponent<{
324325
[onAddNewFieldToCollection]
325326
);
326327

328+
const diagramProps = useMemo(
329+
() => ({
330+
isDarkMode,
331+
title: diagramLabel,
332+
edges,
333+
nodes,
334+
onAddFieldToNodeClick: onClickAddFieldToCollection,
335+
onNodeClick,
336+
onPaneClick,
337+
onEdgeClick,
338+
onFieldClick,
339+
onNodeDragStop,
340+
onConnect,
341+
}),
342+
[
343+
isDarkMode,
344+
diagramLabel,
345+
edges,
346+
nodes,
347+
onClickAddFieldToCollection,
348+
onNodeClick,
349+
onPaneClick,
350+
onEdgeClick,
351+
onFieldClick,
352+
onNodeDragStop,
353+
onConnect,
354+
]
355+
);
356+
357+
const throttledDiagramProps = useThrottledProps(diagramProps);
358+
327359
return (
328360
<div
329361
ref={setDiagramContainerRef}
@@ -346,21 +378,11 @@ const DiagramContent: React.FunctionComponent<{
346378
</Banner>
347379
)}
348380
<Diagram
349-
isDarkMode={isDarkMode}
350-
title={diagramLabel}
351-
edges={edges}
352-
nodes={nodes}
381+
{...throttledDiagramProps}
353382
// With threshold too low clicking sometimes gets confused with
354-
// dragging
383+
// dragging.
355384
nodeDragThreshold={5}
356-
onNodeClick={onNodeClick}
357-
onPaneClick={onPaneClick}
358-
onEdgeClick={onEdgeClick}
359-
onFieldClick={onFieldClick}
360385
fitViewOptions={ZOOM_OPTIONS}
361-
onNodeDragStop={onNodeDragStop}
362-
onConnect={onConnect}
363-
onAddFieldToNodeClick={onClickAddFieldToCollection}
364386
/>
365387
</div>
366388
</div>

0 commit comments

Comments
 (0)