Skip to content

Commit 4875d2e

Browse files
authored
fix: Infinite loop with grid rendering (#1631)
- Caused by #1626 - The children on IrisGrid were getting re-rendered as a result of IrisGrid.handleViewChanged getting called after updating the canvas in Grid.componentDidUpdate - Another fix would be to memoize metrics and not emit a view change if they are exactly the same as previous metrics, but thought that was a bigger change (need to deep equals a bunch of maps and arrays in the metrics) - Tested by opening up a table with React dev tools highlighting re-renders. Table did not re-render when not being interacted with.
1 parent ce7cc3e commit 4875d2e

4 files changed

Lines changed: 97 additions & 1 deletion

File tree

packages/grid/src/Grid.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import React, {
88
import classNames from 'classnames';
99
import memoize from 'memoize-one';
1010
import clamp from 'lodash.clamp';
11-
import { assertNotNull, EMPTY_ARRAY } from '@deephaven/utils';
11+
import { assertNotNull, EMPTY_ARRAY, getChangedKeys } from '@deephaven/utils';
1212
import GridMetricCalculator, { GridMetricState } from './GridMetricCalculator';
1313
import GridModel from './GridModel';
1414
import GridMouseHandler, {
@@ -491,6 +491,17 @@ class Grid extends PureComponent<GridProps, GridState> {
491491
}
492492

493493
componentDidUpdate(prevProps: GridProps, prevState: GridState): void {
494+
const changedProps = getChangedKeys(prevProps, this.props);
495+
const changedState = getChangedKeys(prevState, this.state);
496+
// We don't need to bother re-checking any of the metrics if only the children have changed
497+
if (
498+
changedProps.length === 1 &&
499+
changedProps[0] === 'children' &&
500+
changedState.length === 0
501+
) {
502+
return;
503+
}
504+
494505
const {
495506
isStickyBottom,
496507
isStickyRight,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { getChangedKeys } from './ObjectUtils';
2+
3+
describe('getChangedKeys', () => {
4+
it('should get changed keys', () => {
5+
const oldObject = {
6+
foo: 'bar',
7+
baz: 'qux',
8+
quux: 'corge',
9+
};
10+
const newObject = {
11+
foo: 'bar',
12+
baz: 'quux',
13+
quux: 'corge',
14+
};
15+
expect(getChangedKeys(oldObject, newObject)).toEqual(['baz']);
16+
});
17+
it('should get changed keys when old object is empty', () => {
18+
const oldObject = {};
19+
const newObject = {
20+
foo: 'bar',
21+
baz: 'qux',
22+
quux: 'corge',
23+
};
24+
expect(getChangedKeys(oldObject, newObject)).toEqual([
25+
'foo',
26+
'baz',
27+
'quux',
28+
]);
29+
});
30+
it('should get changed keys when new object is empty', () => {
31+
const oldObject = {
32+
foo: 'bar',
33+
baz: 'qux',
34+
quux: 'corge',
35+
};
36+
const newObject = {};
37+
expect(getChangedKeys(oldObject, newObject)).toEqual([
38+
'foo',
39+
'baz',
40+
'quux',
41+
]);
42+
});
43+
it('should get no changed keys when both objects are empty', () => {
44+
const oldObject = {};
45+
const newObject = {};
46+
expect(getChangedKeys(oldObject, newObject)).toEqual([]);
47+
});
48+
it('should get no changed keys when both objects are the same', () => {
49+
const oldObject = {
50+
foo: 'bar',
51+
baz: 'qux',
52+
quux: 'corge',
53+
};
54+
const newObject = {
55+
foo: 'bar',
56+
baz: 'qux',
57+
quux: 'corge',
58+
};
59+
expect(getChangedKeys(oldObject, newObject)).toEqual([]);
60+
});
61+
});

packages/utils/src/ObjectUtils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Get the keys that have changed between two objects.
3+
* @param oldObject Old object to compare
4+
* @param newObject New object to compare
5+
* @returns Array of keys that have changed
6+
*/
7+
export function getChangedKeys(
8+
oldObject: Record<string, unknown>,
9+
newObject: Record<string, unknown>
10+
): string[] {
11+
const keys = new Set([...Object.keys(oldObject), ...Object.keys(newObject)]);
12+
const changedKeys: string[] = [];
13+
14+
keys.forEach(key => {
15+
if (oldObject[key] !== newObject[key]) {
16+
changedKeys.push(key);
17+
}
18+
});
19+
20+
return changedKeys;
21+
}
22+
23+
export default { getChangedKeys };

packages/utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export { default as Pending } from './Pending';
1515
export * from './PromiseUtils';
1616
export * from './Asserts';
1717
export * from './ErrorUtils';
18+
export * from './ObjectUtils';
1819
export { default as RangeUtils, generateRange } from './RangeUtils';
1920
export type { Range } from './RangeUtils';
2021
export { default as TextUtils } from './TextUtils';

0 commit comments

Comments
 (0)