Skip to content

Commit

Permalink
[EuiDataGrid] Add new cellContext prop/API (#7374)
Browse files Browse the repository at this point in the history
Co-authored-by: Cee Chen <[email protected]>
  • Loading branch information
kqualters-elastic and cee-chen authored Mar 7, 2024
1 parent 5cc4ce8 commit 0396b26
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 4 deletions.
1 change: 1 addition & 0 deletions changelogs/upcoming/7374.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added new `EuiDataGrid` new prop: `cellContext`, an optional object of additional props passed to the cell render function.
13 changes: 13 additions & 0 deletions src-docs/src/views/datagrid/_props_table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ export const DataGridPropsTable: FunctionComponent<{
.filter((i) => !exclude?.includes(i))
.sort();

// Manually move the cellContext prop after renderCellValue
const cellContext = gridPropsKeys.splice(
gridPropsKeys.findIndex((prop) => prop === 'cellContext'),
1
)[0];
if (cellContext) {
gridPropsKeys.splice(
gridPropsKeys.findIndex((prop) => prop === 'renderCellValue') + 1,
0,
cellContext
);
}

const items: BasicItem[] = gridPropsKeys.map((prop) => {
return {
id: prop,
Expand Down
5 changes: 5 additions & 0 deletions src-docs/src/views/datagrid/_snippets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ inMemory={{ level: 'sorting' }}`,
},
]}`,
renderCellValue: 'renderCellValue={({ rowIndex, columnId }) => {}}',
cellContext: `cellContext={{
// Will be passed to your \`renderCellValue\` function/component as a prop
yourData,
}}
renderCellValue={({ rowIndex, columnId, yourData }) => {}}`,
renderCellPopover: `renderCellPopover={({ children, cellActions }) => (
<>
<EuiPopoverTitle>I'm a custom popover!</EuiPopoverTitle>
Expand Down
1 change: 1 addition & 0 deletions src-docs/src/views/datagrid/basics/_props.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const gridLinks = {
ref: '/tabular-content/data-grid-advanced#ref-methods',
renderCustomGridBody:
'/tabular-content/data-grid-advanced#custom-body-renderer',
cellContext: '/tabular-content/data-grid-cells-popovers#cell-context',
};

export const DataGridTopProps = () => {
Expand Down
102 changes: 102 additions & 0 deletions src-docs/src/views/datagrid/cells_popovers/cell_context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { useState, useEffect, useCallback, ReactNode } from 'react';
import { faker } from '@faker-js/faker';

import {
EuiDataGrid,
EuiDataGridColumn,
type RenderCellValue,
EuiButton,
EuiSpacer,
EuiSkeletonText,
} from '../../../../../src';

type DataType = Array<{ [key: string]: ReactNode }>;

const columns: EuiDataGridColumn[] = [
{ id: 'firstName' },
{ id: 'lastName' },
{ id: 'suffix' },
{ id: 'boolean' },
];

const CellValue: RenderCellValue = ({
rowIndex,
columnId,
// Props from cellContext
data,
isLoading,
}) => {
if (isLoading) {
return <EuiSkeletonText lines={1} />;
}

const value = data[rowIndex][columnId];
return value;
};

export default () => {
const [visibleColumns, setVisibleColumns] = useState(
columns.map(({ id }) => id)
);

const [data, setData] = useState<DataType>([]);
const [cellContext, setCellContext] = useState({
data,
isLoading: false,
});

// Mock fetching data from an async API
const mockLoading = useCallback(() => {
setCellContext((context) => ({
...context,
isLoading: true,
}));

// End the loading state after 3 seconds
const timeout = setTimeout(() => {
setCellContext((context) => ({
...context,
isLoading: false,
}));
}, 3000);
return () => clearTimeout(timeout);
}, []);

const fetchData = useCallback(() => {
mockLoading();

const data: DataType = [];
for (let i = 1; i < 5; i++) {
data.push({
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
suffix: faker.person.suffix(),
boolean: `${faker.datatype.boolean()}`,
});
}
setData(data);
setCellContext((context) => ({ ...context, data }));
}, [mockLoading]);

// Fetch data on page load
useEffect(() => {
fetchData();
}, [fetchData]);

return (
<>
<EuiButton size="s" onClick={fetchData}>
Fetch grid data
</EuiButton>
<EuiSpacer size="s" />
<EuiDataGrid
aria-label="Data grid example of cellContext"
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
rowCount={data.length}
renderCellValue={CellValue}
cellContext={cellContext}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import { DataGridCellPopoverExample } from './datagrid_cell_popover_example';
import DataGridFocus from './focus';
const dataGridFocusSource = require('!!raw-loader!./focus');

import CellContext from './cell_context';
const cellContextSource = require('!!raw-loader!./cell_context');

import {
EuiDataGridColumn,
EuiDataGridColumnCellAction,
Expand Down Expand Up @@ -218,5 +221,32 @@ export const DataGridCellsExample = {
),
demo: <DataGridFocus />,
},
{
title: 'Cell context',
source: [
{
type: GuideSectionTypes.TSX,
code: cellContextSource,
},
],
text: (
<>
<p>
The <EuiCode>cellContext</EuiCode> prop is an easy way of passing
your custom data or context from the top level of{' '}
<strong>EuiDataGrid</strong> down to the cell content rendered by
your <EuiCode>renderCellValue</EuiCode> function component.
</p>
<p>
The primary use of the cell context API is performance: if your data
relies on state from your app, it allows you to more easily define
your <EuiCode>renderCellValue</EuiCode> function statically, instead
of within your app, which in turn reduces the number of rerenders
within your data grid.
</p>
</>
),
demo: <CellContext />,
},
],
};
4 changes: 4 additions & 0 deletions src/components/datagrid/body/cell/data_grid_cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const EuiDataGridCellContent: FunctionComponent<
> = memo(
({
renderCellValue,
cellContext,
column,
setCellContentsRef,
rowIndex,
Expand Down Expand Up @@ -99,6 +100,7 @@ const EuiDataGridCellContent: FunctionComponent<
rowIndex={rowIndex}
colIndex={colIndex}
schema={column?.schema || rest.columnType}
{...cellContext}
{...rest}
/>
</div>
Expand Down Expand Up @@ -465,6 +467,7 @@ export class EuiDataGridCell extends Component<
const {
renderCellPopover,
renderCellValue,
cellContext,
rowIndex,
colIndex,
column,
Expand Down Expand Up @@ -492,6 +495,7 @@ export class EuiDataGridCell extends Component<
setCellPopoverProps={setCellPopoverProps}
>
<CellElement
{...cellContext}
{...sharedProps}
setCellProps={this.setCellProps}
isExpandable={true}
Expand Down
3 changes: 3 additions & 0 deletions src/components/datagrid/body/cell/data_grid_cell_wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type CellProps = Pick<
| 'visibleRowIndex'
| 'style'
| 'renderCellValue'
| 'cellContext'
| 'renderCellPopover'
| 'interactiveCellId'
| 'rowHeightsOptions'
Expand Down Expand Up @@ -65,6 +66,7 @@ export const Cell: FunctionComponent<CellProps> = ({
columnWidths,
defaultColumnWidth,
renderCellValue,
cellContext,
renderCellPopover,
interactiveCellId,
setRowHeight,
Expand Down Expand Up @@ -120,6 +122,7 @@ export const Cell: FunctionComponent<CellProps> = ({
rowManager,
popoverContext,
pagination,
cellContext,
};

if (isLeadingControlColumn) {
Expand Down
2 changes: 2 additions & 0 deletions src/components/datagrid/body/data_grid_body_custom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent<
schemaDetectors,
visibleRows,
renderCellValue,
cellContext,
renderCellPopover,
renderFooterCellValue,
interactiveCellId,
Expand Down Expand Up @@ -130,6 +131,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent<
columnWidths,
defaultColumnWidth,
renderCellValue,
cellContext,
renderCellPopover,
interactiveCellId,
setRowHeight,
Expand Down
2 changes: 2 additions & 0 deletions src/components/datagrid/body/data_grid_body_virtualized.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export const EuiDataGridBodyVirtualized: FunctionComponent<
rowCount,
visibleRows: { startRow, endRow, visibleRowCount },
renderCellValue,
cellContext,
renderCellPopover,
renderFooterCellValue,
interactiveCellId,
Expand Down Expand Up @@ -326,6 +327,7 @@ export const EuiDataGridBodyVirtualized: FunctionComponent<
columnWidths,
defaultColumnWidth,
renderCellValue,
cellContext,
renderCellPopover,
interactiveCellId,
rowHeightsOptions,
Expand Down
38 changes: 37 additions & 1 deletion src/components/datagrid/data_grid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import React, { useEffect, useState } from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { EuiDataGrid } from './';
import { EuiDataGridProps } from './data_grid_types';
import type { EuiDataGridProps, RenderCellValue } from './data_grid_types';
import { findTestSubject, requiredProps } from '../../test';
import { render } from '../../test/rtl';
import { EuiDataGridColumnResizer } from './body/header/data_grid_column_resizer';
Expand Down Expand Up @@ -976,6 +976,42 @@ describe('EuiDataGrid', () => {
]
`);
});

it('passes `cellContext` as props to the renderCellValue component', () => {
const dataGridProps = {
'aria-label': 'test',
columns: [{ id: 'Column' }],
columnVisibility: {
visibleColumns: ['Column'],
setVisibleColumns: () => {},
},
rowCount: 1,
};

const RenderCellValueWithContext: RenderCellValue = ({ someContext }) => (
<div data-test-subj="renderedCell">
{someContext ? 'hello' : 'world'}
</div>
);

const { getByTestSubject, rerender } = render(
<EuiDataGrid
{...dataGridProps}
renderCellValue={RenderCellValueWithContext}
cellContext={{ someContext: true }}
/>
);
expect(getByTestSubject('renderedCell')).toHaveTextContent('hello');

rerender(
<EuiDataGrid
{...dataGridProps}
renderCellValue={RenderCellValueWithContext}
cellContext={{ someContext: false }}
/>
);
expect(getByTestSubject('renderedCell')).toHaveTextContent('world');
});
});

describe('pagination', () => {
Expand Down
2 changes: 2 additions & 0 deletions src/components/datagrid/data_grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const EuiDataGrid = memo(
schemaDetectors,
rowCount,
renderCellValue,
cellContext,
renderCellPopover,
renderFooterCellValue,
className,
Expand Down Expand Up @@ -443,6 +444,7 @@ export const EuiDataGrid = memo(
schemaDetectors={allSchemaDetectors}
pagination={pagination}
renderCellValue={renderCellValue}
cellContext={cellContext}
renderCellPopover={renderCellPopover}
renderFooterCellValue={renderFooterCellValue}
rowCount={rowCount}
Expand Down
22 changes: 19 additions & 3 deletions src/components/datagrid/data_grid_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,12 @@ export type CommonGridProps = CommonProps &
* as its only argument.
*/
renderCellValue: EuiDataGridCellProps['renderCellValue'];
/**
* An optional object of props passed to the `renderCellValue` component.
* This API exists to make it easier to define your `renderCellValue` function
* component statically, and not rerender due to other dependent state.
*/
cellContext?: EuiDataGridCellProps['cellContext'];
/**
* An optional function that can be used to completely customize the rendering of cell popovers.
*
Expand Down Expand Up @@ -453,6 +459,7 @@ export interface EuiDataGridBodyProps {
rowCount: number;
visibleRows: EuiDataGridVisibleRows;
renderCellValue: EuiDataGridCellProps['renderCellValue'];
cellContext?: EuiDataGridCellProps['cellContext'];
renderCellPopover?: EuiDataGridCellProps['renderCellPopover'];
renderFooterCellValue?: EuiDataGridCellProps['renderCellValue'];
renderCustomGridBody?: EuiDataGridProps['renderCustomGridBody'];
Expand Down Expand Up @@ -597,6 +604,16 @@ export interface EuiDataGridCellPopoverElementProps
) => void;
}

type CellContext = Omit<
Record<string, any>,
keyof EuiDataGridCellValueElementProps
>;
type CellPropsWithContext = CellContext & EuiDataGridCellValueElementProps;

export type RenderCellValue =
| ((props: CellPropsWithContext) => ReactNode)
| ComponentClass<CellPropsWithContext>;

export interface EuiDataGridCellProps {
rowIndex: number;
visibleRowIndex: number;
Expand All @@ -609,9 +626,8 @@ export interface EuiDataGridCellProps {
isExpandable: boolean;
className?: string;
popoverContext: DataGridCellPopoverContextShape;
renderCellValue:
| ((props: EuiDataGridCellValueElementProps) => ReactNode)
| ComponentClass<EuiDataGridCellValueElementProps>;
renderCellValue: RenderCellValue;
cellContext?: CellContext;
renderCellPopover?:
| JSXElementConstructor<EuiDataGridCellPopoverElementProps>
| ((props: EuiDataGridCellPopoverElementProps) => ReactNode);
Expand Down

0 comments on commit 0396b26

Please sign in to comment.