[Discover] Some cleanups for the new in-table search (#208980)

- Addresses https://github.com/elastic/kibana/issues/208939

## Summary

This PR makes some cleanups to the code introduced in
https://github.com/elastic/kibana/pull/206454 and adds more tests.


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Julia Rechkunova 2025-02-05 08:48:46 +01:00 committed by GitHub
parent d4199dcac1
commit b1b28c3258
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 496 additions and 33 deletions

View file

@ -10,3 +10,4 @@
export { generateMockData } from './data';
export { getRenderCellValueMock } from './render_cell_value_mock';
export { DataGridWithInTableSearchExample } from './data_grid_example';
export { MockContext, useMockContextValue } from './mock_context';

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React from 'react';
interface MockContextValue {
mockContextValue?: string;
}
export const MockContext = React.createContext<MockContextValue>({});
export const useMockContextValue = () => React.useContext(MockContext).mockContextValue;

View file

@ -9,18 +9,28 @@
import React from 'react';
import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
import { useMockContextValue } from './mock_context';
export function getRenderCellValueMock(testData: string[][]) {
return function OriginalRenderCellValue({
colIndex,
rowIndex,
}: EuiDataGridCellValueElementProps) {
const mockContextValue = useMockContextValue();
const cellValue = testData[rowIndex][colIndex];
if (!cellValue) {
throw new Error('Testing unexpected errors');
}
return <div>{cellValue}</div>;
return (
<div>
{cellValue}
{
// testing that it can access the parent context value
mockContextValue ? <span>{mockContextValue}</span> : null
}
</div>
);
};
}

View file

@ -44,7 +44,6 @@ exports[`InTableSearchInput renders input 1`] = `
class="euiText emotion-euiText-s-euiTextColor-subdued"
>
5/10
 
</div>
</div>
<div
@ -140,7 +139,6 @@ exports[`InTableSearchInput renders input when loading 1`] = `
class="euiText emotion-euiText-s-euiTextColor-subdued"
>
0/0
 
</div>
</div>
<div

View file

@ -0,0 +1,386 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React from 'react';
import { render, waitFor, screen } from '@testing-library/react';
import { InTableSearchControl, InTableSearchControlProps } from './in_table_search_control';
import {
CELL_MATCH_INDEX_ATTRIBUTE,
COUNTER_TEST_SUBJ,
HIGHLIGHT_CLASS_NAME,
BUTTON_NEXT_TEST_SUBJ,
} from './constants';
import { wrapRenderCellValueWithInTableSearchSupport } from './wrap_render_cell_value';
import { getRenderCellValueMock } from './__mocks__';
describe('InTableSearchControl', () => {
const testData = [
['aaaa', '100'],
['bbb', 'abb'],
['abc', 'aaac'],
];
const testData2 = [
['bb', 'cc'],
['bc', 'caa'],
];
const visibleColumns = Array.from({ length: 2 }, (_, i) => `column${i}`);
const getColumnIndexFromId = (columnId: string) => parseInt(columnId.replace('column', ''), 10);
it('should update correctly when deps change', async () => {
const initialProps: InTableSearchControlProps = {
inTableSearchTerm: 'a',
pageSize: 10,
visibleColumns,
rows: testData,
renderCellValue: jest.fn(
wrapRenderCellValueWithInTableSearchSupport(getRenderCellValueMock(testData))
),
getColumnIndexFromId: jest.fn(getColumnIndexFromId),
scrollToCell: jest.fn(),
shouldOverrideCmdF: jest.fn(),
onChange: jest.fn(),
onChangeCss: jest.fn(),
onChangeToExpectedPage: jest.fn(),
};
const { rerender } = render(<InTableSearchControl {...initialProps} />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/9');
});
await waitFor(() => {
expect(initialProps.onChangeToExpectedPage).toHaveBeenCalledWith(0);
});
expect(initialProps.getColumnIndexFromId).toHaveBeenCalledWith('column0');
expect(initialProps.scrollToCell).toHaveBeenCalledWith({
align: 'center',
columnIndex: 0,
rowIndex: 0,
});
expect(initialProps.onChange).not.toHaveBeenCalled();
expect(initialProps.onChangeCss).toHaveBeenCalledWith(
expect.objectContaining({
styles: expect.stringContaining(
"[data-gridcell-row-index='0'][data-gridcell-column-id='column0']"
),
})
);
expect(initialProps.onChangeCss).toHaveBeenLastCalledWith(
expect.objectContaining({
styles: expect.stringContaining(
`.${HIGHLIGHT_CLASS_NAME}[${CELL_MATCH_INDEX_ATTRIBUTE}='0']`
),
})
);
rerender(
<InTableSearchControl
{...initialProps}
rows={testData2}
renderCellValue={jest.fn(
wrapRenderCellValueWithInTableSearchSupport(getRenderCellValueMock(testData2))
)}
/>
);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/2');
});
await waitFor(() => {
expect(initialProps.onChangeToExpectedPage).toHaveBeenNthCalledWith(2, 0);
});
expect(initialProps.getColumnIndexFromId).toHaveBeenLastCalledWith('column1');
expect(initialProps.scrollToCell).toHaveBeenLastCalledWith({
align: 'center',
columnIndex: 1,
rowIndex: 1,
});
expect(initialProps.onChange).not.toHaveBeenCalled();
expect(initialProps.onChangeCss).toHaveBeenLastCalledWith(
expect.objectContaining({
styles: expect.stringContaining(
"[data-gridcell-row-index='1'][data-gridcell-column-id='column1']"
),
})
);
expect(initialProps.onChangeCss).toHaveBeenLastCalledWith(
expect.objectContaining({
styles: expect.stringContaining(
`.${HIGHLIGHT_CLASS_NAME}[${CELL_MATCH_INDEX_ATTRIBUTE}='0']`
),
})
);
});
it('should update correctly when search term changes', async () => {
const initialProps: InTableSearchControlProps = {
inTableSearchTerm: 'aa',
pageSize: null,
visibleColumns,
rows: testData,
renderCellValue: jest.fn(
wrapRenderCellValueWithInTableSearchSupport(getRenderCellValueMock(testData))
),
getColumnIndexFromId: jest.fn(getColumnIndexFromId),
scrollToCell: jest.fn(),
shouldOverrideCmdF: jest.fn(),
onChange: jest.fn(),
onChangeCss: jest.fn(),
onChangeToExpectedPage: jest.fn(),
};
const { rerender } = render(<InTableSearchControl {...initialProps} />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/3');
});
rerender(<InTableSearchControl {...initialProps} inTableSearchTerm="b" />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/6');
});
});
it('should change pages correctly', async () => {
const initialProps: InTableSearchControlProps = {
inTableSearchTerm: 'abc',
pageSize: 2,
visibleColumns,
rows: testData,
renderCellValue: jest.fn(
wrapRenderCellValueWithInTableSearchSupport(getRenderCellValueMock(testData))
),
getColumnIndexFromId: jest.fn(getColumnIndexFromId),
scrollToCell: jest.fn(),
shouldOverrideCmdF: jest.fn(),
onChange: jest.fn(),
onChangeCss: jest.fn(),
onChangeToExpectedPage: jest.fn(),
};
const { rerender } = render(<InTableSearchControl {...initialProps} />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/1');
});
expect(initialProps.onChangeToExpectedPage).toHaveBeenCalledWith(1);
rerender(<InTableSearchControl {...initialProps} inTableSearchTerm="c" pageSize={1} />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/2');
});
expect(initialProps.onChangeToExpectedPage).toHaveBeenNthCalledWith(2, 2);
rerender(<InTableSearchControl {...initialProps} inTableSearchTerm="100" />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/1');
});
expect(initialProps.onChangeToExpectedPage).toHaveBeenNthCalledWith(3, 0);
rerender(<InTableSearchControl {...initialProps} inTableSearchTerm="random" />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('0/0');
});
rerender(<InTableSearchControl {...initialProps} inTableSearchTerm="100" pageSize={null} />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/1');
});
expect(initialProps.onChangeToExpectedPage).toHaveBeenCalledTimes(3);
});
it('should highlight the active match correctly', async () => {
const initialProps: InTableSearchControlProps = {
inTableSearchTerm: 'aa',
pageSize: 2,
visibleColumns,
rows: testData,
renderCellValue: jest.fn(
wrapRenderCellValueWithInTableSearchSupport(getRenderCellValueMock(testData))
),
getColumnIndexFromId: jest.fn(getColumnIndexFromId),
scrollToCell: jest.fn(),
shouldOverrideCmdF: jest.fn(),
onChange: jest.fn(),
onChangeCss: jest.fn(),
onChangeToExpectedPage: jest.fn(),
};
render(<InTableSearchControl {...initialProps} />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/3');
});
await waitFor(() => {
expect(initialProps.onChangeToExpectedPage).toHaveBeenCalledWith(0);
});
expect(initialProps.scrollToCell).toHaveBeenCalledWith({
align: 'center',
columnIndex: 0,
rowIndex: 0,
});
expect(initialProps.onChangeCss).toHaveBeenCalledWith(
expect.objectContaining({
styles: expect.stringContaining(
"[data-gridcell-row-index='0'][data-gridcell-column-id='column0']"
),
})
);
expect(initialProps.onChangeCss).toHaveBeenLastCalledWith(
expect.objectContaining({
styles: expect.stringContaining(
`.${HIGHLIGHT_CLASS_NAME}[${CELL_MATCH_INDEX_ATTRIBUTE}='0']`
),
})
);
screen.getByTestId(BUTTON_NEXT_TEST_SUBJ).click();
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('2/3');
});
await waitFor(() => {
expect(initialProps.onChangeToExpectedPage).toHaveBeenNthCalledWith(2, 0);
});
expect(initialProps.scrollToCell).toHaveBeenNthCalledWith(2, {
align: 'center',
columnIndex: 0,
rowIndex: 0,
});
expect(initialProps.onChangeCss).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
styles: expect.stringContaining(
"[data-gridcell-row-index='0'][data-gridcell-column-id='column0']"
),
})
);
expect(initialProps.onChangeCss).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
styles: expect.stringContaining(
`.${HIGHLIGHT_CLASS_NAME}[${CELL_MATCH_INDEX_ATTRIBUTE}='1']`
),
})
);
screen.getByTestId(BUTTON_NEXT_TEST_SUBJ).click();
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('3/3');
});
await waitFor(() => {
expect(initialProps.onChangeToExpectedPage).toHaveBeenNthCalledWith(3, 1);
});
expect(initialProps.scrollToCell).toHaveBeenNthCalledWith(3, {
align: 'center',
columnIndex: 1,
rowIndex: 0,
});
expect(initialProps.onChangeCss).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
styles: expect.stringContaining(
"[data-gridcell-row-index='2'][data-gridcell-column-id='column1']"
),
})
);
expect(initialProps.onChangeCss).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
styles: expect.stringContaining(
`.${HIGHLIGHT_CLASS_NAME}[${CELL_MATCH_INDEX_ATTRIBUTE}='0']`
),
})
);
});
it('should handle timeouts', async () => {
const initialProps: InTableSearchControlProps = {
inTableSearchTerm: 'aa',
pageSize: null,
visibleColumns,
rows: testData,
renderCellValue: jest.fn(
wrapRenderCellValueWithInTableSearchSupport(getRenderCellValueMock(testData))
),
getColumnIndexFromId: jest.fn(getColumnIndexFromId),
scrollToCell: jest.fn(),
shouldOverrideCmdF: jest.fn(),
onChange: jest.fn(),
onChangeCss: jest.fn(),
onChangeToExpectedPage: jest.fn(),
};
const { rerender } = render(<InTableSearchControl {...initialProps} />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/3');
});
rerender(<InTableSearchControl {...initialProps} renderCellValue={jest.fn()} />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('0/0');
});
});
it('should handle ignore errors in cells', async () => {
const initialProps: InTableSearchControlProps = {
inTableSearchTerm: 'aa',
pageSize: null,
visibleColumns: [visibleColumns[0]],
rows: testData,
renderCellValue: jest.fn(
wrapRenderCellValueWithInTableSearchSupport(getRenderCellValueMock(testData))
),
getColumnIndexFromId: jest.fn(getColumnIndexFromId),
scrollToCell: jest.fn(),
shouldOverrideCmdF: jest.fn(),
onChange: jest.fn(),
onChangeCss: jest.fn(),
onChangeToExpectedPage: jest.fn(),
};
const { rerender } = render(<InTableSearchControl {...initialProps} />);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/2');
});
rerender(
<InTableSearchControl {...initialProps} visibleColumns={[...visibleColumns, 'extraColumn']} />
);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/3');
});
});
});

View file

@ -9,6 +9,7 @@
import React, { useCallback, useState, useEffect, useRef } from 'react';
import { EuiButtonIcon, EuiToolTip, useEuiTheme } from '@elastic/eui';
import useEvent from 'react-use/lib/useEvent';
import { i18n } from '@kbn/i18n';
import { css, type SerializedStyles } from '@emotion/react';
import { useFindMatches } from './matches/use_find_matches';
@ -33,7 +34,7 @@ const innerCss = css`
}
.euiFormControlLayout__append {
padding-inline-end: 0 !important;
padding-inline: 0 !important;
background: none;
}
@ -69,8 +70,9 @@ export const InTableSearchControl: React.FC<InTableSearchControlProps> = ({
}) => {
const { euiTheme } = useEuiTheme();
const containerRef = useRef<HTMLDivElement | null>(null);
const buttonRef = useRef<HTMLButtonElement | null>(null);
const shouldReturnFocusToButtonRef = useRef<boolean>(false);
const [isInputVisible, setIsInputVisible] = useState<boolean>(false);
const [isInputVisible, setIsInputVisible] = useState<boolean>(Boolean(props.inTableSearchTerm));
const onScrollToActiveMatch: UseFindMatchesProps['onScrollToActiveMatch'] = useCallback(
({ rowIndex, columnId, matchIndexWithinCell }) => {
@ -133,8 +135,8 @@ export const InTableSearchControl: React.FC<InTableSearchControlProps> = ({
);
// listens for the cmd+f or ctrl+f keydown event to open the input
useEffect(() => {
const handleGlobalKeyDown = (event: KeyboardEvent) => {
const handleGlobalKeyDown = useCallback(
(event: KeyboardEvent) => {
if (
(event.metaKey || event.ctrlKey) &&
event.key === 'f' &&
@ -150,24 +152,17 @@ export const InTableSearchControl: React.FC<InTableSearchControlProps> = ({
) as HTMLInputElement
)?.focus();
}
};
},
[showInput, shouldOverrideCmdF]
);
document.addEventListener('keydown', handleGlobalKeyDown);
return () => {
document.removeEventListener('keydown', handleGlobalKeyDown);
};
}, [showInput, shouldOverrideCmdF]);
useEvent('keydown', handleGlobalKeyDown);
// returns focus to the button when the input was cancelled by pressing the escape key
useEffect(() => {
if (shouldReturnFocusToButtonRef.current && !isInputVisible) {
shouldReturnFocusToButtonRef.current = false;
(
containerRef.current?.querySelector(
`[data-test-subj="${BUTTON_TEST_SUBJ}"]`
) as HTMLButtonElement
)?.focus();
buttonRef.current?.focus();
}
}, [isInputVisible]);
@ -197,6 +192,7 @@ export const InTableSearchControl: React.FC<InTableSearchControlProps> = ({
>
<EuiButtonIcon
data-test-subj={BUTTON_TEST_SUBJ}
buttonRef={buttonRef}
iconType="search"
size="xs"
color="text"

View file

@ -8,7 +8,7 @@
*/
import React, { useEffect, useRef } from 'react';
import { escapeRegExp, memoize } from 'lodash';
import { escapeRegExp } from 'lodash';
import { HIGHLIGHT_COLOR, HIGHLIGHT_CLASS_NAME, CELL_MATCH_INDEX_ATTRIBUTE } from './constants';
import { InTableSearchHighlightsWrapperProps } from './types';
@ -49,9 +49,21 @@ export const InTableSearchHighlightsWrapper: React.FC<InTableSearchHighlightsWra
return <div ref={cellValueRef}>{children}</div>;
};
const getSearchTermRegExp = memoize((searchTerm: string): RegExp => {
return new RegExp(`(${escapeRegExp(searchTerm.trim())})`, 'gi');
});
const searchTermRegExpCache = new Map<string, RegExp>();
const getSearchTermRegExp = (searchTerm: string): RegExp => {
if (searchTermRegExpCache.has(searchTerm)) {
return searchTermRegExpCache.get(searchTerm)!;
}
const searchTermRegExp = new RegExp(`(${escapeRegExp(searchTerm.trim())})`, 'gi');
searchTermRegExpCache.set(searchTerm, searchTermRegExp);
return searchTermRegExp;
};
export const clearSearchTermRegExpCache = () => {
searchTermRegExpCache.clear();
};
function modifyDOMAndAddSearchHighlights(
originalNode: Node,

View file

@ -108,7 +108,6 @@ export const InTableSearchInput: React.FC<InTableSearchInputProps> = React.memo(
{matchesCount && activeMatchPosition
? `${activeMatchPosition}/${matchesCount}`
: '0/0'}
&nbsp;
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React, { useCallback, useEffect, useRef } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { AllCellsProps, RowMatches } from '../types';
const TIMEOUT_PER_ROW = 2000; // 2 sec per row max
@ -28,14 +28,15 @@ export function RowCellsRenderer({
const matchesCountPerColumnIdRef = useRef<Record<string, number>>({});
const rowMatchesCountRef = useRef<number>(0);
const remainingNumberOfResultsRef = useRef<number>(visibleColumns.length);
const isCompletedRef = useRef<boolean>(false);
const hasCompletedRef = useRef<boolean>(false);
const [hasTimedOut, setHasTimedOut] = useState<boolean>(false);
// all cells in the row were processed
const onComplete = useCallback(() => {
if (isCompletedRef.current) {
if (hasCompletedRef.current) {
return;
}
isCompletedRef.current = true; // report only once
hasCompletedRef.current = true; // report only once
onRowProcessed({
rowIndex,
rowMatchesCount: rowMatchesCountRef.current,
@ -69,7 +70,8 @@ export function RowCellsRenderer({
}
timerRef.current = setTimeout(() => {
onCompleteRef.current?.();
onCompleteRef.current?.(); // at least report back the already collected results
setHasTimedOut(true);
}, TIMEOUT_PER_ROW);
return () => {
@ -77,7 +79,12 @@ export function RowCellsRenderer({
clearTimeout(timerRef.current);
}
};
}, [rowIndex]);
}, [rowIndex, setHasTimedOut]);
if (hasTimedOut) {
// stop any further processing
return null;
}
return (
<>

View file

@ -13,6 +13,7 @@ import {
DataGridWithInTableSearchExample,
generateMockData,
getRenderCellValueMock,
MockContext,
} from './__mocks__';
import { useDataGridInTableSearch } from './use_data_grid_in_table_search';
import {
@ -135,4 +136,27 @@ describe('useDataGridInTableSearch', () => {
).toBe(true);
});
});
it('should handle parent contexts correctly', async () => {
render(
<MockContext.Provider value={{ mockContextValue: 'test access to any parent context' }}>
<DataGridWithInTableSearchExample rowsCount={100} columnsCount={2} pageSize={null} />
</MockContext.Provider>
);
screen.getByTestId(BUTTON_TEST_SUBJ).click();
await waitFor(() => {
expect(screen.getByTestId(INPUT_TEST_SUBJ)).toBeInTheDocument();
});
const searchTerm = 'test access';
const input = screen.getByTestId(INPUT_TEST_SUBJ);
fireEvent.change(input, { target: { value: searchTerm } });
expect(input).toHaveValue(searchTerm);
await waitFor(() => {
expect(screen.getByTestId(COUNTER_TEST_SUBJ)).toHaveTextContent('1/200');
});
});
});

View file

@ -7,12 +7,13 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React, { useMemo, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import type { SerializedStyles } from '@emotion/react';
import type { EuiDataGridProps, EuiDataGridRefProps } from '@elastic/eui';
import { InTableSearchControl, InTableSearchControlProps } from './in_table_search_control';
import { RenderCellValueWrapper } from './types';
import { wrapRenderCellValueWithInTableSearchSupport } from './wrap_render_cell_value';
import { clearSearchTermRegExpCache } from './in_table_search_highlights_wrapper';
export interface UseDataGridInTableSearchProps
extends Pick<InTableSearchControlProps, 'rows' | 'visibleColumns'> {
@ -87,7 +88,13 @@ export const useDataGridInTableSearch = (
}
return dataGridWrapper.contains?.(element) ?? false;
}}
onChange={(searchTerm) => setInTableSearchState({ inTableSearchTerm: searchTerm || '' })}
onChange={(searchTerm) => {
const nextSearchTerm = searchTerm || '';
setInTableSearchState({ inTableSearchTerm: nextSearchTerm });
if (!nextSearchTerm) {
clearSearchTermRegExpCache();
}
}}
onChangeCss={(styles) =>
setInTableSearchState((prevState) => ({ ...prevState, inTableSearchTermCss: styles }))
}
@ -123,6 +130,12 @@ export const useDataGridInTableSearch = (
};
}, [cellContext, inTableSearchTerm]);
useEffect(() => {
return () => {
clearSearchTermRegExpCache();
};
}, []);
return useMemo(
() => ({
inTableSearchTermCss,

View file

@ -27,7 +27,6 @@ export interface DataTableContext {
isPlainRecord?: boolean;
pageIndex: number | undefined; // undefined when the pagination is disabled
pageSize: number | undefined;
inTableSearchTerm?: string;
}
const defaultContext = {} as unknown as DataTableContext;