From 47eb66d16f1970c170141bc921c36dc9484aa848 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Sun, 21 Feb 2021 19:30:51 -0300 Subject: [PATCH 01/16] Added isRowExpandable prop to Datagrid component --- docs/List.md | 26 ++++++++++++++++ .../src/list/datagrid/Datagrid.tsx | 4 +++ .../src/list/datagrid/DatagridBody.tsx | 5 +++ .../src/list/datagrid/DatagridRow.tsx | 31 +++++++++++++------ 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/docs/List.md b/docs/List.md index 3586d408647..2479b688d9c 100644 --- a/docs/List.md +++ b/docs/List.md @@ -1978,6 +1978,7 @@ Here are all the props accepted by the component: * [`rowStyle`](#row-style-function) * [`rowClick`](#rowclick) * [`expand`](#expand) +* [`isRowExpandable`](#isrowexpandable) * [`isRowSelectable`](#isrowselectable) * [`optimized`](#performance) @@ -2198,6 +2199,31 @@ const PostList = props => ( ) ``` +### `isRowExpandable` + +You can customize which rows will allow to show an expandable panel below them using the `isRowExpandable` prop. It expects a function that will receive the record of each `` and returns a boolean expression. For instance, this code shows an expand button only for rows with an id greater than 300: + +```jsx +const PostPanel = ({ id, record, resource }) => ( +
+); + +const PostList = props => ( + + } + isRowExpandable={row => row.id > 300} + > + + + + + + + +) +``` + ### `isRowSelectable` You can customize which rows will show a selection checkbox using the `isRowSelectable` prop. It expects a function that will receive the record of each `` and returns a boolean expression. For instance, this code shows a checkbox only for rows with an id greater than 300: diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx index 3f25423a42f..c26d6cbd84d 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx @@ -116,6 +116,7 @@ const Datagrid: FC = React.forwardRef((props, ref) => { hasBulkActions = false, hover, isRowSelectable, + isRowExpandable, resource, rowClick, rowStyle, @@ -318,6 +319,7 @@ const Datagrid: FC = React.forwardRef((props, ref) => { rowStyle, selectedIds, isRowSelectable, + isRowExpandable, version, }, children @@ -353,6 +355,7 @@ Datagrid.propTypes = { total: PropTypes.number, version: PropTypes.number, isRowSelectable: PropTypes.func, + isRowExpandable: PropTypes.func, }; type RowClickFunction = ( @@ -376,6 +379,7 @@ export interface DatagridProps extends Omit { hasBulkActions?: boolean; hover?: boolean; isRowSelectable?: (record: Record) => boolean; + isRowExpandable?: (record: Record) => boolean; optimized?: boolean; rowClick?: string | RowClickFunction; rowStyle?: (record: Record, index: number) => any; diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridBody.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridBody.tsx index e56f17bb579..73d06b20c9d 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridBody.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridBody.tsx @@ -28,6 +28,7 @@ const DatagridBody: FC = React.forwardRef( rowStyle, selectedIds, isRowSelectable, + isRowExpandable, ...rest }, ref @@ -57,6 +58,8 @@ const DatagridBody: FC = React.forwardRef( record: data[id], resource, rowClick, + expandable: + !isRowExpandable || isRowExpandable(data[id]), selectable: !isRowSelectable || isRowSelectable(data[id]), selected: selectedIds?.includes(id), @@ -89,6 +92,7 @@ DatagridBody.propTypes = { selectedIds: PropTypes.arrayOf(PropTypes.any), styles: PropTypes.object, isRowSelectable: PropTypes.func, + isRowExpandable: PropTypes.func, }; DatagridBody.defaultProps = { @@ -131,6 +135,7 @@ export interface DatagridBodyProps extends Omit { rowStyle?: (record: Record, index: number) => any; selectedIds?: Identifier[]; isRowSelectable?: (record: Record) => boolean; + isRowExpandable?: (record: Record) => boolean; } // trick material-ui Table into thinking this is one of the child type it supports diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx index 467c9836b81..6ca7a620aee 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx @@ -57,22 +57,28 @@ const DatagridRow: FC = React.forwardRef((props, ref) => { selected, style, selectable, + expandable, ...rest } = props; + const isExpandable = expandable && expand; const resource = useResourceContext(props); const [expanded, toggleExpanded] = useExpanded(resource, id); const [nbColumns, setNbColumns] = useState( - computeNbColumns(expand, children, hasBulkActions) + computeNbColumns(isExpandable, children, hasBulkActions) ); useEffect(() => { // Fields can be hidden dynamically based on permissions; // The expand panel must span over the remaining columns // So we must recompute the number of columns to span on - const newNbColumns = computeNbColumns(expand, children, hasBulkActions); + const newNbColumns = computeNbColumns( + isExpandable, + children, + hasBulkActions + ); if (newNbColumns !== nbColumns) { setNbColumns(newNbColumns); } - }, [expand, nbColumns, children, hasBulkActions]); + }, [isExpandable, nbColumns, children, hasBulkActions]); const history = useHistory(); @@ -145,12 +151,14 @@ const DatagridRow: FC = React.forwardRef((props, ref) => { padding="none" className={classes.expandIconCell} > - + {expandable && ( + + )} )} {hasBulkActions && ( @@ -181,7 +189,7 @@ const DatagridRow: FC = React.forwardRef((props, ref) => { ) : null )} - {expand && expanded && ( + {isExpandable && expanded && ( {isValidElement(expand) @@ -224,6 +232,7 @@ DatagridRow.propTypes = { selected: PropTypes.bool, style: PropTypes.object, selectable: PropTypes.bool, + expandable: PropTypes.bool, }; DatagridRow.defaultProps = { @@ -231,6 +240,7 @@ DatagridRow.defaultProps = { hover: true, selected: false, selectable: true, + expandable: true, }; export interface DatagridRowProps @@ -259,6 +269,7 @@ export interface DatagridRowProps selected?: boolean; style?: any; selectable?: boolean; + expandable?: boolean; } export type RowClickFunction = ( From b14fb743a2f2921f57a3a622620f74d28790a2d6 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Mon, 22 Feb 2021 12:04:51 -0300 Subject: [PATCH 02/16] Improve docs --- docs/List.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/List.md b/docs/List.md index 2479b688d9c..2c3aa6983b3 100644 --- a/docs/List.md +++ b/docs/List.md @@ -2201,7 +2201,7 @@ const PostList = props => ( ### `isRowExpandable` -You can customize which rows will allow to show an expandable panel below them using the `isRowExpandable` prop. It expects a function that will receive the record of each `` and returns a boolean expression. For instance, this code shows an expand button only for rows with an id greater than 300: +You can customize which rows will allow to show an expandable panel below them using the `isRowExpandable` prop. It expects a function that will receive the record of each `` and returns a boolean expression. For instance, this code shows an expand button only for rows that has a detail to show: ```jsx const PostPanel = ({ id, record, resource }) => ( @@ -2212,7 +2212,7 @@ const PostList = props => ( } - isRowExpandable={row => row.id > 300} + isRowExpandable={row => row.has_detail} > From ec10784fe2d0e35f594241f9019e1e7395711b47 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Mon, 22 Feb 2021 15:28:26 -0300 Subject: [PATCH 03/16] Added DatagridContext, DatagridProvider and useDatagridContext hook --- .../src/list/datagrid/DatagridContext.ts | 12 ++++++++++++ .../src/list/datagrid/DatagridProvider.tsx | 16 ++++++++++++++++ .../src/list/datagrid/useDatagridContext.ts | 7 +++++++ 3 files changed, 35 insertions(+) create mode 100644 packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts create mode 100644 packages/ra-ui-materialui/src/list/datagrid/DatagridProvider.tsx create mode 100644 packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts b/packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts new file mode 100644 index 00000000000..e70b0e5baeb --- /dev/null +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts @@ -0,0 +1,12 @@ +import { createContext } from 'react'; +import { Record as RaRecord } from 'ra-core'; + +const DatagridContext = createContext(undefined); + +DatagridContext.displayName = 'DatagridContext'; + +export type DatagridContextValue = { + isRowExpandable?: (record: RaRecord) => boolean; +}; + +export default DatagridContext; diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridProvider.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridProvider.tsx new file mode 100644 index 00000000000..3370fd2f98a --- /dev/null +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridProvider.tsx @@ -0,0 +1,16 @@ +import React, { ReactElement, ReactNode } from 'react'; +import DatagridContext, { DatagridContextValue } from './DatagridContext'; + +const DatagridProvider = ({ + children, + value, +}: { + children: ReactNode; + value: DatagridContextValue; +}): ReactElement => ( + + {children} + +); + +export default DatagridProvider; diff --git a/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts new file mode 100644 index 00000000000..abb08cce977 --- /dev/null +++ b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts @@ -0,0 +1,7 @@ +import { useContext } from 'react'; +import DatagridContext, { DatagridContextValue } from './DatagridContext'; + +export const useDatagridContext = (): DatagridContextValue => { + const context = useContext(DatagridContext); + return context; +}; From 5dc7fbfde08afd41eb8cfd90f46237440634a311 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Mon, 22 Feb 2021 15:31:05 -0300 Subject: [PATCH 04/16] Implement DatagridContext in Datagrid component --- .../src/list/datagrid/Datagrid.tsx | 156 +++++++++--------- 1 file changed, 80 insertions(+), 76 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx index c26d6cbd84d..8c3c92ac158 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx @@ -34,6 +34,7 @@ import DatagridLoading from './DatagridLoading'; import DatagridBody, { PureDatagridBody } from './DatagridBody'; import useDatagridStyles from './useDatagridStyles'; import { ClassesOverride } from '../../types'; +import DatagridProvider from './DatagridProvider'; /** * The Datagrid component renders a list of records as a table. @@ -247,84 +248,87 @@ const Datagrid: FC = React.forwardRef((props, ref) => { * the datagrid displays the current data. */ return ( - - - - {expand && ( - - )} - {hasBulkActions && ( - - 0 && - all.length > 0 && - all.every(id => selectedIds.includes(id)) - } - onChange={handleSelectAll} + +
+ + + {expand && ( + - - )} - {Children.map(children, (field, index) => - isValidElement(field) ? ( - - ) : null - )} - - - {cloneElement( - body, - { - basePath, - className: classes.tbody, - classes, - expand, - rowClick, - data, - hasBulkActions, - hover, - ids, - onToggleItem: handleToggleItem, - resource, - rowStyle, - selectedIds, - isRowSelectable, - isRowExpandable, - version, - }, - children - )} -
+ > + 0 && + all.length > 0 && + all.every(id => + selectedIds.includes(id) + ) + } + onChange={handleSelectAll} + /> +
+ )} + {Children.map(children, (field, index) => + isValidElement(field) ? ( + + ) : null + )} +
+ + {cloneElement( + body, + { + basePath, + className: classes.tbody, + classes, + expand, + rowClick, + data, + hasBulkActions, + hover, + ids, + onToggleItem: handleToggleItem, + resource, + rowStyle, + selectedIds, + isRowSelectable, + version, + }, + children + )} + + ); }); From 8b221ad60595e82862bc3016218ae325d9826b57 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Mon, 22 Feb 2021 15:32:08 -0300 Subject: [PATCH 05/16] Take isRowExpandable value from Datagrid context and revert changes to DatagridBody component --- .../src/list/datagrid/DatagridBody.tsx | 5 ----- .../src/list/datagrid/DatagridRow.tsx | 21 +++++++++++-------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridBody.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridBody.tsx index 73d06b20c9d..e56f17bb579 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridBody.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridBody.tsx @@ -28,7 +28,6 @@ const DatagridBody: FC = React.forwardRef( rowStyle, selectedIds, isRowSelectable, - isRowExpandable, ...rest }, ref @@ -58,8 +57,6 @@ const DatagridBody: FC = React.forwardRef( record: data[id], resource, rowClick, - expandable: - !isRowExpandable || isRowExpandable(data[id]), selectable: !isRowSelectable || isRowSelectable(data[id]), selected: selectedIds?.includes(id), @@ -92,7 +89,6 @@ DatagridBody.propTypes = { selectedIds: PropTypes.arrayOf(PropTypes.any), styles: PropTypes.object, isRowSelectable: PropTypes.func, - isRowExpandable: PropTypes.func, }; DatagridBody.defaultProps = { @@ -135,7 +131,6 @@ export interface DatagridBodyProps extends Omit { rowStyle?: (record: Record, index: number) => any; selectedIds?: Identifier[]; isRowSelectable?: (record: Record) => boolean; - isRowExpandable?: (record: Record) => boolean; } // trick material-ui Table into thinking this is one of the child type it supports diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx index 6ca7a620aee..a62b2f2382c 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx @@ -31,6 +31,7 @@ import { useHistory } from 'react-router-dom'; import DatagridCell from './DatagridCell'; import ExpandRowButton from './ExpandRowButton'; import useDatagridStyles from './useDatagridStyles'; +import { useDatagridContext } from './useDatagridContext'; const computeNbColumns = (expand, children, hasBulkActions) => expand @@ -57,28 +58,33 @@ const DatagridRow: FC = React.forwardRef((props, ref) => { selected, style, selectable, - expandable, ...rest } = props; - const isExpandable = expandable && expand; + + const context = useDatagridContext(); + const expandable = + (!context || + !context.isRowExpandable || + context.isRowExpandable(record)) && + expand; const resource = useResourceContext(props); const [expanded, toggleExpanded] = useExpanded(resource, id); const [nbColumns, setNbColumns] = useState( - computeNbColumns(isExpandable, children, hasBulkActions) + computeNbColumns(expandable, children, hasBulkActions) ); useEffect(() => { // Fields can be hidden dynamically based on permissions; // The expand panel must span over the remaining columns // So we must recompute the number of columns to span on const newNbColumns = computeNbColumns( - isExpandable, + expandable, children, hasBulkActions ); if (newNbColumns !== nbColumns) { setNbColumns(newNbColumns); } - }, [isExpandable, nbColumns, children, hasBulkActions]); + }, [expandable, nbColumns, children, hasBulkActions]); const history = useHistory(); @@ -189,7 +195,7 @@ const DatagridRow: FC = React.forwardRef((props, ref) => { ) : null )} - {isExpandable && expanded && ( + {expandable && expanded && ( {isValidElement(expand) @@ -232,7 +238,6 @@ DatagridRow.propTypes = { selected: PropTypes.bool, style: PropTypes.object, selectable: PropTypes.bool, - expandable: PropTypes.bool, }; DatagridRow.defaultProps = { @@ -240,7 +245,6 @@ DatagridRow.defaultProps = { hover: true, selected: false, selectable: true, - expandable: true, }; export interface DatagridRowProps @@ -269,7 +273,6 @@ export interface DatagridRowProps selected?: boolean; style?: any; selectable?: boolean; - expandable?: boolean; } export type RowClickFunction = ( From ded4512d6c61f7333b4121228709970f1a04a401 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Mon, 22 Feb 2021 15:33:11 -0300 Subject: [PATCH 06/16] Fix DatagridRow tests to use DatagridContext --- .../src/list/datagrid/DatagridRow.spec.tsx | 93 +++++++++++-------- 1 file changed, 56 insertions(+), 37 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx index e2f691cfbf1..e7e9fbe4b76 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx @@ -6,6 +6,7 @@ import { createMemoryHistory } from 'history'; import { Router } from 'react-router-dom'; import DatagridRow from './DatagridRow'; +import DatagridProvider from './DatagridProvider'; const TitleField = ({ record }: any): JSX.Element => ( {record.title} @@ -39,6 +40,8 @@ describe('', () => { resource: 'posts', }; + const dataGridContextValue = { isRowExpandable: undefined }; + const renderWithRouter = children => { const history = createMemoryHistory(); @@ -51,9 +54,11 @@ describe('', () => { describe('rowClick', () => { it("should redirect to edit page if the 'edit' option is selected", () => { const { getByText, history } = renderWithRouter( - - - + + + + + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual( @@ -63,9 +68,11 @@ describe('', () => { it("should redirect to show page if the 'show' option is selected", () => { const { getByText, history } = renderWithRouter( - - - + + + + + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual( @@ -75,13 +82,15 @@ describe('', () => { it("should change the expand state if the 'expand' option is selected", () => { const { queryAllByText, getByText } = renderWithRouter( - } - > - - + + } + > + + + ); expect(queryAllByText('expanded')).toHaveLength(0); fireEvent.click(getByText('hello')); @@ -93,13 +102,15 @@ describe('', () => { it("should execute the onToggleItem function if the 'toggleSelection' option is selected", () => { const onToggleItem = jest.fn(); const { getByText } = renderWithRouter( - - - + + + + + ); fireEvent.click(getByText('hello')); expect(onToggleItem.mock.calls.length).toEqual(1); @@ -108,14 +119,16 @@ describe('', () => { it('should not execute the onToggleItem function if the row is not selectable', () => { const onToggleItem = jest.fn(); const { getByText } = renderWithRouter( - - - + + + + + ); fireEvent.click(getByText('hello')); expect(onToggleItem).not.toHaveBeenCalled(); @@ -124,9 +137,11 @@ describe('', () => { it('should redirect to the custom path if onRowClick is a string', () => { const path = '/foo/bar'; const { getByText, history } = renderWithRouter( - - - + + + + + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual(path); @@ -135,9 +150,11 @@ describe('', () => { it('should evaluate the function and redirect to the result of that function if onRowClick is a custom function', async () => { const customRowClick = () => '/bar/foo'; const { getByText, history } = renderWithRouter( - - - + + + + + ); fireEvent.click(getByText('hello')); await new Promise(setImmediate); // waitFor one tick @@ -146,9 +163,11 @@ describe('', () => { it('should not call push if onRowClick is falsy', () => { const { getByText, history } = renderWithRouter( - - - + + + + + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual('/'); From 61517439455ebcd7b64f76f0136e08bd29d72557 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Mon, 22 Feb 2021 15:58:32 -0300 Subject: [PATCH 07/16] Renamed DatagridProvider to DatagridContextProvider --- .../src/list/datagrid/Datagrid.tsx | 6 ++-- ...ovider.tsx => DatagridContextProvider.tsx} | 4 +-- .../src/list/datagrid/DatagridRow.spec.tsx | 34 +++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) rename packages/ra-ui-materialui/src/list/datagrid/{DatagridProvider.tsx => DatagridContextProvider.tsx} (82%) diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx index 8c3c92ac158..b2bcbe1c338 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx @@ -34,7 +34,7 @@ import DatagridLoading from './DatagridLoading'; import DatagridBody, { PureDatagridBody } from './DatagridBody'; import useDatagridStyles from './useDatagridStyles'; import { ClassesOverride } from '../../types'; -import DatagridProvider from './DatagridProvider'; +import DatagridContextProvider from './DatagridContextProvider'; /** * The Datagrid component renders a list of records as a table. @@ -248,7 +248,7 @@ const Datagrid: FC = React.forwardRef((props, ref) => { * the datagrid displays the current data. */ return ( - + = React.forwardRef((props, ref) => { children )}
-
+ ); }); diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridProvider.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridContextProvider.tsx similarity index 82% rename from packages/ra-ui-materialui/src/list/datagrid/DatagridProvider.tsx rename to packages/ra-ui-materialui/src/list/datagrid/DatagridContextProvider.tsx index 3370fd2f98a..fb0baf972f5 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridProvider.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridContextProvider.tsx @@ -1,7 +1,7 @@ import React, { ReactElement, ReactNode } from 'react'; import DatagridContext, { DatagridContextValue } from './DatagridContext'; -const DatagridProvider = ({ +const DatagridContextProvider = ({ children, value, }: { @@ -13,4 +13,4 @@ const DatagridProvider = ({ ); -export default DatagridProvider; +export default DatagridContextProvider; diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx index e7e9fbe4b76..1a68e810b92 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx @@ -6,7 +6,7 @@ import { createMemoryHistory } from 'history'; import { Router } from 'react-router-dom'; import DatagridRow from './DatagridRow'; -import DatagridProvider from './DatagridProvider'; +import DatagridContextProvider from './DatagridContextProvider'; const TitleField = ({ record }: any): JSX.Element => ( {record.title} @@ -54,11 +54,11 @@ describe('', () => { describe('rowClick', () => { it("should redirect to edit page if the 'edit' option is selected", () => { const { getByText, history } = renderWithRouter( - + - + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual( @@ -68,11 +68,11 @@ describe('', () => { it("should redirect to show page if the 'show' option is selected", () => { const { getByText, history } = renderWithRouter( - + - + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual( @@ -82,7 +82,7 @@ describe('', () => { it("should change the expand state if the 'expand' option is selected", () => { const { queryAllByText, getByText } = renderWithRouter( - + ', () => { > - + ); expect(queryAllByText('expanded')).toHaveLength(0); fireEvent.click(getByText('hello')); @@ -102,7 +102,7 @@ describe('', () => { it("should execute the onToggleItem function if the 'toggleSelection' option is selected", () => { const onToggleItem = jest.fn(); const { getByText } = renderWithRouter( - + ', () => { > - + ); fireEvent.click(getByText('hello')); expect(onToggleItem.mock.calls.length).toEqual(1); @@ -119,7 +119,7 @@ describe('', () => { it('should not execute the onToggleItem function if the row is not selectable', () => { const onToggleItem = jest.fn(); const { getByText } = renderWithRouter( - + ', () => { > - + ); fireEvent.click(getByText('hello')); expect(onToggleItem).not.toHaveBeenCalled(); @@ -137,11 +137,11 @@ describe('', () => { it('should redirect to the custom path if onRowClick is a string', () => { const path = '/foo/bar'; const { getByText, history } = renderWithRouter( - + - + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual(path); @@ -150,11 +150,11 @@ describe('', () => { it('should evaluate the function and redirect to the result of that function if onRowClick is a custom function', async () => { const customRowClick = () => '/bar/foo'; const { getByText, history } = renderWithRouter( - + - + ); fireEvent.click(getByText('hello')); await new Promise(setImmediate); // waitFor one tick @@ -163,11 +163,11 @@ describe('', () => { it('should not call push if onRowClick is falsy', () => { const { getByText, history } = renderWithRouter( - + - + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual('/'); From 63e6db597891f9146b6a47c2b26432f734fe2e6c Mon Sep 17 00:00:00 2001 From: asvarcas Date: Mon, 22 Feb 2021 16:00:12 -0300 Subject: [PATCH 08/16] Throw error if no context is found --- .../src/list/datagrid/useDatagridContext.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts index abb08cce977..7f148b54fa2 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts +++ b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts @@ -3,5 +3,12 @@ import DatagridContext, { DatagridContextValue } from './DatagridContext'; export const useDatagridContext = (): DatagridContextValue => { const context = useContext(DatagridContext); + + if (!context) { + throw new Error( + 'useDatagridContext must be used inside a DatagridContextProvider' + ); + } + return context; }; From 8ec93ccfb805d327157267e5c526b92d2273ba99 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Tue, 23 Feb 2021 09:54:02 -0300 Subject: [PATCH 09/16] Applied review --- .../src/list/datagrid/Datagrid.tsx | 7 ++++- .../src/list/datagrid/useDatagridContext.ts | 30 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx index b2bcbe1c338..14c4566fa89 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx @@ -8,6 +8,7 @@ import { useEffect, FC, ReactElement, + useMemo, } from 'react'; import PropTypes from 'prop-types'; import { @@ -242,13 +243,17 @@ const Datagrid: FC = React.forwardRef((props, ref) => { ? ids.filter(id => isRowSelectable(data[id])) : ids; + const contextValue = useMemo(() => ({ isRowExpandable }), [ + isRowExpandable, + ]); + /** * After the initial load, if the data for the list isn't empty, * and even if the data is refreshing (e.g. after a filter change), * the datagrid displays the current data. */ return ( - + { +export const useDatagridContext = ( + props?: DatagridProps +): DatagridContextValue => { const context = useContext(DatagridContext); - if (!context) { - throw new Error( - 'useDatagridContext must be used inside a DatagridContextProvider' - ); - } + warning( + !context, + `useDatagridContext must be used inside a DatagridContextProvider` + ); - return context; + return useMemo( + () => + merge( + {}, + context, + props != null + ? { isRowExpandable: props.isRowExpandable } + : { isRowExpandable: undefined } + ), + [context, props] + ); }; From 5a12517fdb6faa53e5cfeecf1725df73ba425a21 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Tue, 23 Feb 2021 10:03:54 -0300 Subject: [PATCH 10/16] Relocated use of useMemo --- packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx index 14c4566fa89..17e69d8d1c4 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx @@ -140,6 +140,10 @@ const Datagrid: FC = React.forwardRef((props, ref) => { } = useListContext(props); const version = useVersion(); + const contextValue = useMemo(() => ({ isRowExpandable }), [ + isRowExpandable, + ]); + const updateSort = useCallback( event => { event.stopPropagation(); @@ -243,10 +247,6 @@ const Datagrid: FC = React.forwardRef((props, ref) => { ? ids.filter(id => isRowSelectable(data[id])) : ids; - const contextValue = useMemo(() => ({ isRowExpandable }), [ - isRowExpandable, - ]); - /** * After the initial load, if the data for the list isn't empty, * and even if the data is refreshing (e.g. after a filter change), From a158d6b5fdfeccbca36f5018a07602db39183335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C3=ADbal=20Svarcas?= Date: Wed, 24 Feb 2021 10:29:41 -0300 Subject: [PATCH 11/16] Update packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts Co-authored-by: Francois Zaninotto --- .../ra-ui-materialui/src/list/datagrid/useDatagridContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts index 89af3069603..e244a0f4758 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts +++ b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts @@ -21,7 +21,7 @@ export const useDatagridContext = ( context, props != null ? { isRowExpandable: props.isRowExpandable } - : { isRowExpandable: undefined } + : { } ), [context, props] ); From 6f4f9ed592a70eb7a69560d62958e4a2713b02e8 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Wed, 24 Feb 2021 10:32:44 -0300 Subject: [PATCH 12/16] Remove warning --- .../src/list/datagrid/useDatagridContext.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts index e244a0f4758..9628c14bd1e 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts +++ b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts @@ -9,19 +9,12 @@ export const useDatagridContext = ( ): DatagridContextValue => { const context = useContext(DatagridContext); - warning( - !context, - `useDatagridContext must be used inside a DatagridContextProvider` - ); - return useMemo( () => merge( {}, context, - props != null - ? { isRowExpandable: props.isRowExpandable } - : { } + props != null ? { isRowExpandable: props.isRowExpandable } : {} ), [context, props] ); From a6f69c9f9c0e1d7462f57f8c9f240835334f8ba6 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Wed, 24 Feb 2021 10:39:16 -0300 Subject: [PATCH 13/16] Use a default DatagridContextValue in tests --- .../ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx index 1a68e810b92..18bb5c2a25a 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx @@ -40,7 +40,7 @@ describe('', () => { resource: 'posts', }; - const dataGridContextValue = { isRowExpandable: undefined }; + const dataGridContextValue = {}; const renderWithRouter = children => { const history = createMemoryHistory(); From f894eaab7ccf6ae5e859fc62bd5ac8e7db09103c Mon Sep 17 00:00:00 2001 From: asvarcas Date: Wed, 24 Feb 2021 11:01:00 -0300 Subject: [PATCH 14/16] Added tests for isRowExpandable prop --- .../src/list/datagrid/DatagridRow.spec.tsx | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx index 18bb5c2a25a..f621017cd0a 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx @@ -51,6 +51,46 @@ describe('', () => { }; }; + describe('isRowExpandable', () => { + it('should show the expand button if it returns true', () => { + const contextValue = { isRowExpandable: () => true }; + + const { queryAllByText, getByText } = renderWithRouter( + + } + > + + + + ); + expect(queryAllByText('expanded')).toHaveLength(0); + fireEvent.click(getByText('hello')); + expect(queryAllByText('expanded')).toHaveLength(0); + }); + + it('should not show the expand button if it returns false', () => { + const contextValue = { isRowExpandable: () => false }; + + const { queryAllByText, getByText } = renderWithRouter( + + } + > + + + + ); + expect(queryAllByText('expanded')).toHaveLength(0); + fireEvent.click(getByText('hello')); + expect(queryAllByText('expanded')).toHaveLength(1); + }); + }); + describe('rowClick', () => { it("should redirect to edit page if the 'edit' option is selected", () => { const { getByText, history } = renderWithRouter( From 1837d9522c47edd0de7ffd9d03be5d2730826074 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Wed, 24 Feb 2021 11:26:45 -0300 Subject: [PATCH 15/16] Fix tests --- .../ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx index f621017cd0a..8491836526f 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx @@ -68,7 +68,7 @@ describe('', () => { ); expect(queryAllByText('expanded')).toHaveLength(0); fireEvent.click(getByText('hello')); - expect(queryAllByText('expanded')).toHaveLength(0); + expect(queryAllByText('expanded')).toHaveLength(1); }); it('should not show the expand button if it returns false', () => { @@ -87,7 +87,7 @@ describe('', () => { ); expect(queryAllByText('expanded')).toHaveLength(0); fireEvent.click(getByText('hello')); - expect(queryAllByText('expanded')).toHaveLength(1); + expect(queryAllByText('expanded')).toHaveLength(0); }); }); From 71aeac92267c479edeaec64b34b1c700c8d6a168 Mon Sep 17 00:00:00 2001 From: asvarcas Date: Fri, 26 Feb 2021 07:49:55 -0300 Subject: [PATCH 16/16] Applied review --- .../src/list/datagrid/DatagridContext.ts | 2 +- .../src/list/datagrid/DatagridRow.spec.tsx | 92 ++++++++----------- .../src/list/datagrid/useDatagridContext.ts | 1 - 3 files changed, 38 insertions(+), 57 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts b/packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts index e70b0e5baeb..119658216ef 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridContext.ts @@ -1,7 +1,7 @@ import { createContext } from 'react'; import { Record as RaRecord } from 'ra-core'; -const DatagridContext = createContext(undefined); +const DatagridContext = createContext({}); DatagridContext.displayName = 'DatagridContext'; diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx index 8491836526f..d2c23fb20b8 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.spec.tsx @@ -40,8 +40,6 @@ describe('', () => { resource: 'posts', }; - const dataGridContextValue = {}; - const renderWithRouter = children => { const history = createMemoryHistory(); @@ -94,11 +92,9 @@ describe('', () => { describe('rowClick', () => { it("should redirect to edit page if the 'edit' option is selected", () => { const { getByText, history } = renderWithRouter( - - - - - + + + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual( @@ -108,11 +104,9 @@ describe('', () => { it("should redirect to show page if the 'show' option is selected", () => { const { getByText, history } = renderWithRouter( - - - - - + + + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual( @@ -122,15 +116,13 @@ describe('', () => { it("should change the expand state if the 'expand' option is selected", () => { const { queryAllByText, getByText } = renderWithRouter( - - } - > - - - + } + > + + ); expect(queryAllByText('expanded')).toHaveLength(0); fireEvent.click(getByText('hello')); @@ -142,15 +134,13 @@ describe('', () => { it("should execute the onToggleItem function if the 'toggleSelection' option is selected", () => { const onToggleItem = jest.fn(); const { getByText } = renderWithRouter( - - - - - + + + ); fireEvent.click(getByText('hello')); expect(onToggleItem.mock.calls.length).toEqual(1); @@ -159,16 +149,14 @@ describe('', () => { it('should not execute the onToggleItem function if the row is not selectable', () => { const onToggleItem = jest.fn(); const { getByText } = renderWithRouter( - - - - - + + + ); fireEvent.click(getByText('hello')); expect(onToggleItem).not.toHaveBeenCalled(); @@ -177,11 +165,9 @@ describe('', () => { it('should redirect to the custom path if onRowClick is a string', () => { const path = '/foo/bar'; const { getByText, history } = renderWithRouter( - - - - - + + + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual(path); @@ -190,11 +176,9 @@ describe('', () => { it('should evaluate the function and redirect to the result of that function if onRowClick is a custom function', async () => { const customRowClick = () => '/bar/foo'; const { getByText, history } = renderWithRouter( - - - - - + + + ); fireEvent.click(getByText('hello')); await new Promise(setImmediate); // waitFor one tick @@ -203,11 +187,9 @@ describe('', () => { it('should not call push if onRowClick is falsy', () => { const { getByText, history } = renderWithRouter( - - - - - + + + ); fireEvent.click(getByText('hello')); expect(history.location.pathname).toEqual('/'); diff --git a/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts index 9628c14bd1e..726843d94c7 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts +++ b/packages/ra-ui-materialui/src/list/datagrid/useDatagridContext.ts @@ -2,7 +2,6 @@ import { useContext, useMemo } from 'react'; import { DatagridProps } from './Datagrid'; import DatagridContext, { DatagridContextValue } from './DatagridContext'; import merge from 'lodash/merge'; -import { warning } from 'ra-core'; export const useDatagridContext = ( props?: DatagridProps