Skip to content

Commit 9cf4a4d

Browse files
committed
[7962] Allow consumer changes to display settings after user changes have been made
- requires updating how reset button behaves, and storing initial display settings in refs on mount + reorder/organize code with comment blocks / by concern - modify deep equal to potentially not cost as much / re-run isEqual as often if the object *is* correctly memoized
1 parent 81b5e25 commit 9cf4a4d

3 files changed

Lines changed: 151 additions & 73 deletions

File tree

packages/eui/src/components/datagrid/controls/display_selector.test.tsx

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,6 @@ describe('useDataGridDisplaySelector', () => {
545545

546546
describe('reset button', () => {
547547
it('renders a reset button only when the user changes from the current settings', async () => {
548-
// const component = mount(<MockComponent gridStyles={startingStyles} />);
549548
const { container, baseElement, getByTestSubject } = render(
550549
<MockComponent gridStyles={startingStyles} />
551550
);
@@ -582,7 +581,32 @@ describe('useDataGridDisplaySelector', () => {
582581
).not.toBeInTheDocument();
583582
});
584583

585-
it('hides the reset button even after changes if allowResetButton is false', async () => {
584+
it('hides the reset button if the user changes display settings back to the initial settings', async () => {
585+
const { container, baseElement, getByTestSubject } = render(
586+
<MockComponent gridStyles={startingStyles} />
587+
);
588+
openPopover(container);
589+
590+
await waitFor(() => {
591+
userEvent.click(getByTestSubject('expanded'));
592+
userEvent.click(getByTestSubject('auto'));
593+
});
594+
595+
expect(
596+
baseElement.querySelector('[data-test-subj="resetDisplaySelector"]')
597+
).toBeInTheDocument();
598+
599+
await waitFor(() => {
600+
userEvent.click(getByTestSubject('normal'));
601+
userEvent.click(getByTestSubject('undefined'));
602+
});
603+
604+
expect(
605+
container.querySelector('[data-test-subj="resetDisplaySelector"]')
606+
).not.toBeInTheDocument();
607+
});
608+
609+
it('does not render the reset button if allowResetButton is false', async () => {
586610
const { container, baseElement, getByTestSubject } = render(
587611
<MockComponent
588612
showDisplaySelector={{
@@ -603,16 +627,6 @@ describe('useDataGridDisplaySelector', () => {
603627
expect(
604628
baseElement.querySelector('[data-test-subj="resetDisplaySelector"]')
605629
).not.toBeInTheDocument();
606-
607-
// Should hide the reset button again after the popover was reopened
608-
closePopover(container);
609-
await waitForEuiPopoverClose();
610-
openPopover(container);
611-
await waitForEuiPopoverOpen();
612-
613-
expect(
614-
baseElement.querySelector('[data-test-subj="resetDisplaySelector"]')
615-
).not.toBeInTheDocument();
616630
});
617631
});
618632

@@ -658,6 +672,17 @@ describe('useDataGridDisplaySelector', () => {
658672
}
659673
`);
660674
});
675+
676+
it('updates gridStyles when consumers pass in new settings', () => {
677+
const { result, rerender } = renderHook(
678+
({ gridStyles }) => useDataGridDisplaySelector(true, gridStyles),
679+
{ initialProps: { gridStyles: startingStyles } }
680+
);
681+
expect(result.current[1].border).toEqual('all');
682+
683+
rerender({ gridStyles: { ...startingStyles, border: 'none' } });
684+
expect(result.current[1].border).toEqual('none');
685+
});
661686
});
662687

663688
describe('rowHeightsOptions', () => {
@@ -687,6 +712,7 @@ describe('useDataGridDisplaySelector', () => {
687712
const getOutput = () => {
688713
return JSON.parse(screen.getByTestSubject('output').textContent!);
689714
};
715+
690716
describe('returns an object of rowHeightsOptions with user overrides', () => {
691717
it('overrides `rowHeights` and `defaultHeight`', () => {
692718
render(
@@ -703,6 +729,7 @@ describe('useDataGridDisplaySelector', () => {
703729
defaultHeight: undefined,
704730
});
705731
});
732+
706733
it('does not override other rowHeightsOptions properties', () => {
707734
render(
708735
<MockComponent initialRowHeightsOptions={{ lineHeight: '2em' }} />
@@ -714,6 +741,21 @@ describe('useDataGridDisplaySelector', () => {
714741
rowHeights: {},
715742
});
716743
});
744+
745+
it('updates rowHeightsOptions when consumers pass in new settings', () => {
746+
const initialRowHeightsOptions: EuiDataGridRowHeightsOptions = {
747+
defaultHeight: 'auto',
748+
};
749+
const { result, rerender } = renderHook(
750+
({ rowHeightsOptions }) =>
751+
useDataGridDisplaySelector(true, startingStyles, rowHeightsOptions),
752+
{ initialProps: { rowHeightsOptions: initialRowHeightsOptions } }
753+
);
754+
expect(result.current[2].defaultHeight).toEqual('auto');
755+
756+
rerender({ rowHeightsOptions: { defaultHeight: { lineCount: 2 } } });
757+
expect(result.current[2].defaultHeight).toEqual({ lineCount: 2 });
758+
});
717759
});
718760

719761
it('handles undefined initialRowHeightsOptions', () => {

packages/eui/src/components/datagrid/controls/display_selector.tsx

Lines changed: 88 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import React, {
1212
useMemo,
1313
useCallback,
1414
useEffect,
15+
useRef,
1516
} from 'react';
1617

1718
import { logicalStyle, mathWithUnits } from '../../../global_styling';
18-
import { useUpdateEffect, useEuiTheme } from '../../../services';
19+
import { useUpdateEffect, useDeepEqual, useEuiTheme } from '../../../services';
1920
import { EuiI18n, useEuiI18n } from '../../i18n';
2021
import { EuiPopover, EuiPopoverFooter } from '../../popover';
2122
import { EuiButtonIcon, EuiButtonGroup, EuiButtonEmpty } from '../../button';
@@ -41,6 +42,7 @@ export const startingStyles: EuiDataGridStyle = {
4142
footer: 'overline',
4243
stickyFooter: true,
4344
};
45+
const emptyRowHeightsOptions: EuiDataGridRowHeightsOptions = {};
4446

4547
/**
4648
* Cell density
@@ -281,8 +283,8 @@ const RowHeightControl = ({
281283

282284
export const useDataGridDisplaySelector = (
283285
showDisplaySelector: EuiDataGridToolBarVisibilityOptions['showDisplaySelector'],
284-
initialStyles: EuiDataGridStyle,
285-
initialRowHeightsOptions?: EuiDataGridRowHeightsOptions
286+
passedGridStyles: EuiDataGridStyle,
287+
passedRowHeightsOptions: EuiDataGridRowHeightsOptions = emptyRowHeightsOptions
286288
): [ReactNode, EuiDataGridStyle, EuiDataGridRowHeightsOptions] => {
287289
const [isOpen, setIsOpen] = useState(false);
288290

@@ -296,79 +298,110 @@ export const useDataGridDisplaySelector = (
296298
? undefined
297299
: showDisplaySelector?.customRender;
298300

299-
// Track styles specified by the user at run time
300-
const [userGridStyles, setUserGridStyles] = useState({});
301-
const [userRowHeightsOptions, setUserRowHeightsOptions] = useState({});
302-
303-
// Merge the developer-specified configurations with user overrides
304-
const gridStyles = useMemo(() => {
305-
return {
306-
...initialStyles,
307-
...userGridStyles,
308-
};
309-
}, [initialStyles, userGridStyles]);
310-
311-
const rowHeightsOptions = useMemo(() => {
312-
return {
313-
...initialRowHeightsOptions,
314-
...userRowHeightsOptions,
315-
};
316-
}, [initialRowHeightsOptions, userRowHeightsOptions]);
317-
318-
// Invoke onChange callbacks (removing the callback value itself, so that only configuration values are returned)
319-
useUpdateEffect(() => {
320-
const { onChange, ...currentGridStyles } = gridStyles;
321-
initialStyles?.onChange?.(currentGridStyles);
322-
}, [userGridStyles]);
301+
/**
302+
* Grid style changes
303+
*/
304+
const [gridStyles, setGridStyles] =
305+
useState<EuiDataGridStyle>(passedGridStyles);
323306

307+
// Update if consumers pass new grid style configurations
308+
const stablePassedGridStyles = useDeepEqual(passedGridStyles);
324309
useUpdateEffect(() => {
325-
const { onChange, ...currentRowHeightsOptions } = rowHeightsOptions;
326-
initialRowHeightsOptions?.onChange?.(currentRowHeightsOptions);
327-
}, [userRowHeightsOptions]);
328-
329-
// Allow resetting to initial developer-specified configurations
330-
const resetToInitialState = useCallback(() => {
331-
setUserGridStyles({});
332-
setUserRowHeightsOptions({});
333-
}, []);
310+
setGridStyles(stablePassedGridStyles);
311+
}, [stablePassedGridStyles]);
312+
313+
// Update on user display selector change
314+
const onUserGridStyleChange = useCallback(
315+
(styles: EuiDataGridRowHeightsOptions) =>
316+
setGridStyles((prevStyles) => {
317+
const changedStyles = { ...prevStyles, ...styles };
318+
const { onChange, ...rest } = changedStyles;
319+
onChange?.(rest);
320+
return changedStyles;
321+
}),
322+
[]
323+
);
334324

335325
const densityControl = useMemo(() => {
336326
const show = getNestedObjectOptions(showDisplaySelector, 'allowDensity');
337327
return show ? (
338-
<DensityControl gridStyles={gridStyles} onChange={setUserGridStyles} />
328+
<DensityControl
329+
gridStyles={gridStyles}
330+
onChange={onUserGridStyleChange}
331+
/>
339332
) : null;
340-
}, [showDisplaySelector, gridStyles, setUserGridStyles]);
333+
}, [showDisplaySelector, gridStyles, onUserGridStyleChange]);
334+
335+
/**
336+
* Row height changes
337+
*/
338+
const [rowHeightsOptions, setRowHeightsOptions] =
339+
useState<EuiDataGridRowHeightsOptions>(passedRowHeightsOptions);
340+
341+
// Update if consumers pass new row height configurations
342+
const stablePassedRowHeights = useDeepEqual(passedRowHeightsOptions);
343+
useUpdateEffect(() => {
344+
setRowHeightsOptions(stablePassedRowHeights);
345+
}, [stablePassedRowHeights]);
346+
347+
// Update on user display selector change
348+
const onUserRowHeightChange = useCallback(
349+
(options: EuiDataGridRowHeightsOptions) =>
350+
setRowHeightsOptions((prevOptions) => {
351+
const changedOptions = { ...prevOptions, ...options };
352+
const { onChange, ...rest } = changedOptions;
353+
onChange?.(rest);
354+
return changedOptions;
355+
}),
356+
[]
357+
);
341358

342359
const rowHeightControl = useMemo(() => {
343360
const show = getNestedObjectOptions(showDisplaySelector, 'allowRowHeight');
344361
return show ? (
345362
<RowHeightControl
346363
rowHeightsOptions={rowHeightsOptions}
347-
onChange={setUserRowHeightsOptions}
364+
onChange={onUserRowHeightChange}
348365
/>
349366
) : null;
350-
}, [showDisplaySelector, rowHeightsOptions, setUserRowHeightsOptions]);
367+
}, [showDisplaySelector, rowHeightsOptions, onUserRowHeightChange]);
368+
369+
/**
370+
* Reset button
371+
*/
372+
const [showResetButton, setShowResetButton] = useState(false);
373+
const initialGridStyles = useRef<EuiDataGridStyle>(passedGridStyles);
374+
const initialRowHeightsOptions = useRef<EuiDataGridRowHeightsOptions>(
375+
passedRowHeightsOptions
376+
);
377+
378+
const resetToInitialState = useCallback(() => {
379+
setGridStyles(initialGridStyles.current);
380+
setRowHeightsOptions(initialRowHeightsOptions.current);
381+
}, []);
382+
383+
useUpdateEffect(() => {
384+
setShowResetButton(
385+
rowHeightsOptions.defaultHeight !==
386+
initialRowHeightsOptions.current.defaultHeight ||
387+
gridStyles.fontSize !== initialGridStyles.current.fontSize ||
388+
gridStyles.cellPadding !== initialGridStyles.current.cellPadding
389+
);
390+
}, [
391+
rowHeightsOptions.defaultHeight,
392+
gridStyles.fontSize,
393+
gridStyles.cellPadding,
394+
]);
351395

352396
const resetButton = useMemo(() => {
353-
const show = getNestedObjectOptions(
397+
const allowed = getNestedObjectOptions(
354398
showDisplaySelector,
355399
'allowResetButton'
356400
);
357-
if (!show) return null;
358-
359-
const hasUserChanges =
360-
Object.keys(userGridStyles).length > 0 ||
361-
Object.keys(userRowHeightsOptions).length > 0;
401+
if (!allowed || !showResetButton) return null;
362402

363-
return hasUserChanges ? (
364-
<ResetButton onClick={resetToInitialState} />
365-
) : null;
366-
}, [
367-
showDisplaySelector,
368-
resetToInitialState,
369-
userGridStyles,
370-
userRowHeightsOptions,
371-
]);
403+
return <ResetButton onClick={resetToInitialState} />;
404+
}, [showDisplaySelector, showResetButton, resetToInitialState]);
372405

373406
const buttonLabel = useEuiI18n(
374407
'euiDisplaySelector.buttonText',

packages/eui/src/services/hooks/useDeepEqual.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
* Side Public License, v 1.
77
*/
88

9-
import { useRef } from 'react';
9+
import { useState } from 'react';
1010
import isEqual from 'lodash/isEqual';
11+
import { useUpdateEffect } from './useUpdateEffect';
1112

1213
/**
1314
* This hook is mostly a performance concern for third-party objs/arrays that EUI
@@ -17,11 +18,13 @@ import isEqual from 'lodash/isEqual';
1718
export const useDeepEqual = <T = Record<string, any> | any[] | undefined>(
1819
object: T
1920
): T => {
20-
const ref = useRef(object);
21+
const [memoizedObject, setMemoizedObject] = useState(object);
2122

22-
if (!isEqual(object, ref.current)) {
23-
ref.current = object;
24-
}
23+
useUpdateEffect(() => {
24+
if (!isEqual(object, memoizedObject)) {
25+
setMemoizedObject(object);
26+
}
27+
}, [object]);
2528

26-
return ref.current;
29+
return memoizedObject;
2730
};

0 commit comments

Comments
 (0)