diff --git a/packages/eui/src/components/datagrid/controls/display_selector.test.tsx b/packages/eui/src/components/datagrid/controls/display_selector.test.tsx
index 41717b8843f..e13a39a19c0 100644
--- a/packages/eui/src/components/datagrid/controls/display_selector.test.tsx
+++ b/packages/eui/src/components/datagrid/controls/display_selector.test.tsx
@@ -545,7 +545,6 @@ describe('useDataGridDisplaySelector', () => {
describe('reset button', () => {
it('renders a reset button only when the user changes from the current settings', async () => {
- // const component = mount();
const { container, baseElement, getByTestSubject } = render(
);
@@ -582,7 +581,32 @@ describe('useDataGridDisplaySelector', () => {
).not.toBeInTheDocument();
});
- it('hides the reset button even after changes if allowResetButton is false', async () => {
+ it('hides the reset button if the user changes display settings back to the initial settings', async () => {
+ const { container, baseElement, getByTestSubject } = render(
+
+ );
+ openPopover(container);
+
+ await waitFor(() => {
+ userEvent.click(getByTestSubject('expanded'));
+ userEvent.click(getByTestSubject('auto'));
+ });
+
+ expect(
+ baseElement.querySelector('[data-test-subj="resetDisplaySelector"]')
+ ).toBeInTheDocument();
+
+ await waitFor(() => {
+ userEvent.click(getByTestSubject('normal'));
+ userEvent.click(getByTestSubject('undefined'));
+ });
+
+ expect(
+ container.querySelector('[data-test-subj="resetDisplaySelector"]')
+ ).not.toBeInTheDocument();
+ });
+
+ it('does not render the reset button if allowResetButton is false', async () => {
const { container, baseElement, getByTestSubject } = render(
{
expect(
baseElement.querySelector('[data-test-subj="resetDisplaySelector"]')
).not.toBeInTheDocument();
-
- // Should hide the reset button again after the popover was reopened
- closePopover(container);
- await waitForEuiPopoverClose();
- openPopover(container);
- await waitForEuiPopoverOpen();
-
- expect(
- baseElement.querySelector('[data-test-subj="resetDisplaySelector"]')
- ).not.toBeInTheDocument();
});
});
@@ -658,6 +672,17 @@ describe('useDataGridDisplaySelector', () => {
}
`);
});
+
+ it('updates gridStyles when consumers pass in new settings', () => {
+ const { result, rerender } = renderHook(
+ ({ gridStyles }) => useDataGridDisplaySelector(true, gridStyles),
+ { initialProps: { gridStyles: startingStyles } }
+ );
+ expect(result.current[1].border).toEqual('all');
+
+ rerender({ gridStyles: { ...startingStyles, border: 'none' } });
+ expect(result.current[1].border).toEqual('none');
+ });
});
describe('rowHeightsOptions', () => {
@@ -687,6 +712,7 @@ describe('useDataGridDisplaySelector', () => {
const getOutput = () => {
return JSON.parse(screen.getByTestSubject('output').textContent!);
};
+
describe('returns an object of rowHeightsOptions with user overrides', () => {
it('overrides `rowHeights` and `defaultHeight`', () => {
render(
@@ -703,6 +729,7 @@ describe('useDataGridDisplaySelector', () => {
defaultHeight: undefined,
});
});
+
it('does not override other rowHeightsOptions properties', () => {
render(
@@ -714,6 +741,21 @@ describe('useDataGridDisplaySelector', () => {
rowHeights: {},
});
});
+
+ it('updates rowHeightsOptions when consumers pass in new settings', () => {
+ const initialRowHeightsOptions: EuiDataGridRowHeightsOptions = {
+ defaultHeight: 'auto',
+ };
+ const { result, rerender } = renderHook(
+ ({ rowHeightsOptions }) =>
+ useDataGridDisplaySelector(true, startingStyles, rowHeightsOptions),
+ { initialProps: { rowHeightsOptions: initialRowHeightsOptions } }
+ );
+ expect(result.current[2].defaultHeight).toEqual('auto');
+
+ rerender({ rowHeightsOptions: { defaultHeight: { lineCount: 2 } } });
+ expect(result.current[2].defaultHeight).toEqual({ lineCount: 2 });
+ });
});
it('handles undefined initialRowHeightsOptions', () => {
diff --git a/packages/eui/src/components/datagrid/controls/display_selector.tsx b/packages/eui/src/components/datagrid/controls/display_selector.tsx
index a930304a248..c07a101064d 100644
--- a/packages/eui/src/components/datagrid/controls/display_selector.tsx
+++ b/packages/eui/src/components/datagrid/controls/display_selector.tsx
@@ -12,10 +12,11 @@ import React, {
useMemo,
useCallback,
useEffect,
+ useRef,
} from 'react';
import { logicalStyle, mathWithUnits } from '../../../global_styling';
-import { useUpdateEffect, useEuiTheme } from '../../../services';
+import { useUpdateEffect, useDeepEqual, useEuiTheme } from '../../../services';
import { EuiI18n, useEuiI18n } from '../../i18n';
import { EuiPopover, EuiPopoverFooter } from '../../popover';
import { EuiButtonIcon, EuiButtonGroup, EuiButtonEmpty } from '../../button';
@@ -41,6 +42,7 @@ export const startingStyles: EuiDataGridStyle = {
footer: 'overline',
stickyFooter: true,
};
+const emptyRowHeightsOptions: EuiDataGridRowHeightsOptions = {};
/**
* Cell density
@@ -281,8 +283,8 @@ const RowHeightControl = ({
export const useDataGridDisplaySelector = (
showDisplaySelector: EuiDataGridToolBarVisibilityOptions['showDisplaySelector'],
- initialStyles: EuiDataGridStyle,
- initialRowHeightsOptions?: EuiDataGridRowHeightsOptions
+ passedGridStyles: EuiDataGridStyle,
+ passedRowHeightsOptions: EuiDataGridRowHeightsOptions = emptyRowHeightsOptions
): [ReactNode, EuiDataGridStyle, EuiDataGridRowHeightsOptions] => {
const [isOpen, setIsOpen] = useState(false);
@@ -296,79 +298,110 @@ export const useDataGridDisplaySelector = (
? undefined
: showDisplaySelector?.customRender;
- // Track styles specified by the user at run time
- const [userGridStyles, setUserGridStyles] = useState({});
- const [userRowHeightsOptions, setUserRowHeightsOptions] = useState({});
-
- // Merge the developer-specified configurations with user overrides
- const gridStyles = useMemo(() => {
- return {
- ...initialStyles,
- ...userGridStyles,
- };
- }, [initialStyles, userGridStyles]);
-
- const rowHeightsOptions = useMemo(() => {
- return {
- ...initialRowHeightsOptions,
- ...userRowHeightsOptions,
- };
- }, [initialRowHeightsOptions, userRowHeightsOptions]);
-
- // Invoke onChange callbacks (removing the callback value itself, so that only configuration values are returned)
- useUpdateEffect(() => {
- const { onChange, ...currentGridStyles } = gridStyles;
- initialStyles?.onChange?.(currentGridStyles);
- }, [userGridStyles]);
+ /**
+ * Grid style changes
+ */
+ const [gridStyles, setGridStyles] =
+ useState(passedGridStyles);
+ // Update if consumers pass new grid style configurations
+ const stablePassedGridStyles = useDeepEqual(passedGridStyles);
useUpdateEffect(() => {
- const { onChange, ...currentRowHeightsOptions } = rowHeightsOptions;
- initialRowHeightsOptions?.onChange?.(currentRowHeightsOptions);
- }, [userRowHeightsOptions]);
-
- // Allow resetting to initial developer-specified configurations
- const resetToInitialState = useCallback(() => {
- setUserGridStyles({});
- setUserRowHeightsOptions({});
- }, []);
+ setGridStyles(stablePassedGridStyles);
+ }, [stablePassedGridStyles]);
+
+ // Update on user display selector change
+ const onUserGridStyleChange = useCallback(
+ (styles: EuiDataGridRowHeightsOptions) =>
+ setGridStyles((prevStyles) => {
+ const changedStyles = { ...prevStyles, ...styles };
+ const { onChange, ...rest } = changedStyles;
+ onChange?.(rest);
+ return changedStyles;
+ }),
+ []
+ );
const densityControl = useMemo(() => {
const show = getNestedObjectOptions(showDisplaySelector, 'allowDensity');
return show ? (
-
+
) : null;
- }, [showDisplaySelector, gridStyles, setUserGridStyles]);
+ }, [showDisplaySelector, gridStyles, onUserGridStyleChange]);
+
+ /**
+ * Row height changes
+ */
+ const [rowHeightsOptions, setRowHeightsOptions] =
+ useState(passedRowHeightsOptions);
+
+ // Update if consumers pass new row height configurations
+ const stablePassedRowHeights = useDeepEqual(passedRowHeightsOptions);
+ useUpdateEffect(() => {
+ setRowHeightsOptions(stablePassedRowHeights);
+ }, [stablePassedRowHeights]);
+
+ // Update on user display selector change
+ const onUserRowHeightChange = useCallback(
+ (options: EuiDataGridRowHeightsOptions) =>
+ setRowHeightsOptions((prevOptions) => {
+ const changedOptions = { ...prevOptions, ...options };
+ const { onChange, ...rest } = changedOptions;
+ onChange?.(rest);
+ return changedOptions;
+ }),
+ []
+ );
const rowHeightControl = useMemo(() => {
const show = getNestedObjectOptions(showDisplaySelector, 'allowRowHeight');
return show ? (
) : null;
- }, [showDisplaySelector, rowHeightsOptions, setUserRowHeightsOptions]);
+ }, [showDisplaySelector, rowHeightsOptions, onUserRowHeightChange]);
+
+ /**
+ * Reset button
+ */
+ const [showResetButton, setShowResetButton] = useState(false);
+ const initialGridStyles = useRef(passedGridStyles);
+ const initialRowHeightsOptions = useRef(
+ passedRowHeightsOptions
+ );
+
+ const resetToInitialState = useCallback(() => {
+ setGridStyles(initialGridStyles.current);
+ setRowHeightsOptions(initialRowHeightsOptions.current);
+ }, []);
+
+ useUpdateEffect(() => {
+ setShowResetButton(
+ rowHeightsOptions.defaultHeight !==
+ initialRowHeightsOptions.current.defaultHeight ||
+ gridStyles.fontSize !== initialGridStyles.current.fontSize ||
+ gridStyles.cellPadding !== initialGridStyles.current.cellPadding
+ );
+ }, [
+ rowHeightsOptions.defaultHeight,
+ gridStyles.fontSize,
+ gridStyles.cellPadding,
+ ]);
const resetButton = useMemo(() => {
- const show = getNestedObjectOptions(
+ const allowed = getNestedObjectOptions(
showDisplaySelector,
'allowResetButton'
);
- if (!show) return null;
-
- const hasUserChanges =
- Object.keys(userGridStyles).length > 0 ||
- Object.keys(userRowHeightsOptions).length > 0;
+ if (!allowed || !showResetButton) return null;
- return hasUserChanges ? (
-
- ) : null;
- }, [
- showDisplaySelector,
- resetToInitialState,
- userGridStyles,
- userRowHeightsOptions,
- ]);
+ return ;
+ }, [showDisplaySelector, showResetButton, resetToInitialState]);
const buttonLabel = useEuiI18n(
'euiDisplaySelector.buttonText',
diff --git a/packages/eui/src/services/hooks/useDeepEqual.ts b/packages/eui/src/services/hooks/useDeepEqual.ts
index e00fd890081..a608aa1c33a 100644
--- a/packages/eui/src/services/hooks/useDeepEqual.ts
+++ b/packages/eui/src/services/hooks/useDeepEqual.ts
@@ -6,8 +6,9 @@
* Side Public License, v 1.
*/
-import { useRef } from 'react';
+import { useState } from 'react';
import isEqual from 'lodash/isEqual';
+import { useUpdateEffect } from './useUpdateEffect';
/**
* This hook is mostly a performance concern for third-party objs/arrays that EUI
@@ -17,11 +18,13 @@ import isEqual from 'lodash/isEqual';
export const useDeepEqual = | any[] | undefined>(
object: T
): T => {
- const ref = useRef(object);
+ const [memoizedObject, setMemoizedObject] = useState(object);
- if (!isEqual(object, ref.current)) {
- ref.current = object;
- }
+ useUpdateEffect(() => {
+ if (!isEqual(object, memoizedObject)) {
+ setMemoizedObject(object);
+ }
+ }, [object]);
- return ref.current;
+ return memoizedObject;
};