diff --git a/docs/Datagrid.md b/docs/Datagrid.md
index ebd87eb010a..d0653cedf1a 100644
--- a/docs/Datagrid.md
+++ b/docs/Datagrid.md
@@ -368,8 +368,9 @@ const CustomResetViewsButton = () => {
## `empty`
-It's possible that a Datagrid will have no records to display. If the Datagrid's parent component handles the loading state, the Datagrid will return `null` and render nothing.
-Passing through a component to the `empty` prop will cause the Datagrid to render the `empty` component instead of `null`.
+It's possible that a Datagrid will have no records to display. If the Datagrid's parent component does not handle the empty state, the Datagrid will display a message indicating there are no results. This message is translatable and its key is `ra.navigation.no_results`.
+
+You can customize the empty state by passing a component to the `empty` prop:
```jsx
const CustomEmpty = () =>
No books found
;
diff --git a/docs/SimpleList.md b/docs/SimpleList.md
index 541a62bfdda..684d2485b97 100644
--- a/docs/SimpleList.md
+++ b/docs/SimpleList.md
@@ -44,6 +44,7 @@ It accepts the following props:
* [`rightAvatar`](#rightavatar)
* [`rightIcon`](#righticon)
* [`rowStyle`](#rowstyle)
+* [`empty`](#empty)
## `leftAvatar`
@@ -179,6 +180,25 @@ See [`primaryText`](#primarytext)
See [`primaryText`](#primarytext)
+## `empty`
+
+It's possible that a SimpleList will have no records to display. If the SimpleList's parent component does not handle the empty state, the SimpleList will display a message indicating there are no results. This message is translatable and its key is `ra.navigation.no_results`.
+
+You can customize the empty state by passing a component to the `empty` prop:
+
+```jsx
+const CustomEmpty = () => No books found
;
+
+const PostList = () => (
+
+ record.title}
+ empty={}
+ />
+
+);
+```
+
## Using `` On Small Screens
To use `` on small screens and a `` on larger screens, use MUI's `useMediaQuery` hook:
diff --git a/packages/ra-ui-materialui/src/list/ListNoResults.tsx b/packages/ra-ui-materialui/src/list/ListNoResults.tsx
new file mode 100644
index 00000000000..1f2f8acd97a
--- /dev/null
+++ b/packages/ra-ui-materialui/src/list/ListNoResults.tsx
@@ -0,0 +1,17 @@
+import * as React from 'react';
+import { memo } from 'react';
+import CardContent from '@mui/material/CardContent';
+import Typography from '@mui/material/Typography';
+import { useResourceContext, useTranslate } from 'ra-core';
+
+export const ListNoResults = memo(() => {
+ const translate = useTranslate();
+ const resource = useResourceContext();
+ return (
+
+
+ {translate('ra.navigation.no_results', { resource })}
+
+
+ );
+});
diff --git a/packages/ra-ui-materialui/src/list/ListView.tsx b/packages/ra-ui-materialui/src/list/ListView.tsx
index 9931925b5be..c4f0c98d182 100644
--- a/packages/ra-ui-materialui/src/list/ListView.tsx
+++ b/packages/ra-ui-materialui/src/list/ListView.tsx
@@ -1,12 +1,6 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
-import {
- Children,
- cloneElement,
- ReactElement,
- ReactNode,
- ElementType,
-} from 'react';
+import { cloneElement, ReactElement, ReactNode, ElementType } from 'react';
import PropTypes from 'prop-types';
import { SxProps } from '@mui/system';
import Card from '@mui/material/Card';
diff --git a/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.spec.tsx b/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.spec.tsx
index d87b7305f04..b1664d11584 100644
--- a/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.spec.tsx
@@ -130,4 +130,20 @@ describe('', () => {
expect(screen.getByText('2').closest('a')).toBeNull();
});
});
+
+ it('should display a message when there is no result', () => {
+ render(
+
+
+
+ );
+ expect(screen.queryByText('ra.navigation.no_results')).not.toBeNull();
+ });
});
diff --git a/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.tsx b/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.tsx
index da1195ce6a5..575bc06c1b4 100644
--- a/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.tsx
+++ b/packages/ra-ui-materialui/src/list/SimpleList/SimpleList.tsx
@@ -27,6 +27,7 @@ import {
} from 'ra-core';
import { SimpleListLoading } from './SimpleListLoading';
+import { ListNoResults } from '../ListNoResults';
/**
* The component renders a list of records as a MUI .
@@ -67,6 +68,7 @@ export const SimpleList = (
) => {
const {
className,
+ empty = DefaultEmpty,
hasBulkActions,
leftAvatar,
leftIcon,
@@ -95,6 +97,18 @@ export const SimpleList = (
);
}
+ /**
+ * Once loaded, the data for the list may be empty. Instead of
+ * displaying the table header with zero data rows,
+ * the SimpleList the empty component.
+ */
+ if (data == null || data.length === 0 || total === 0) {
+ if (empty) {
+ return empty;
+ }
+
+ return null;
+ }
const renderAvatar = (
record: RecordType,
avatarCallback: FunctionToElement
@@ -245,6 +259,7 @@ export type FunctionToElement = (
export interface SimpleListProps
extends Omit {
className?: string;
+ empty?: ReactElement;
hasBulkActions?: boolean;
leftAvatar?: FunctionToElement;
leftIcon?: FunctionToElement;
@@ -321,3 +336,5 @@ const Root = styled(List, {
})({
[`& .${SimpleListClasses.tertiary}`]: { float: 'right', opacity: 0.541176 },
});
+
+const DefaultEmpty = ;
diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx
index 9b140899ab4..6259abd5cdd 100644
--- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.spec.tsx
@@ -268,4 +268,13 @@ describe('', () => {
expect(contextValue.onSelect).toHaveBeenCalledTimes(1);
});
});
+
+ it('should display a message when there is no result', () => {
+ render(
+
+
+
+ );
+ expect(screen.queryByText('ra.navigation.no_results')).not.toBeNull();
+ });
});
diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx
index 37b8bad5de0..0c55fa3abab 100644
--- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx
+++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx
@@ -32,6 +32,7 @@ import DatagridContextProvider from './DatagridContextProvider';
import { DatagridClasses, DatagridRoot } from './useDatagridStyles';
import { BulkActionsToolbar } from '../BulkActionsToolbar';
import { BulkDeleteButton } from '../../button';
+import { ListNoResults } from '../ListNoResults';
const defaultBulkActionButtons = ;
@@ -120,7 +121,7 @@ export const Datagrid: FC = React.forwardRef((props, ref) => {
header = DatagridHeader,
children,
className,
- empty,
+ empty = DefaultEmpty,
expand,
bulkActionButtons = defaultBulkActionButtons,
hover,
@@ -210,7 +211,7 @@ export const Datagrid: FC = React.forwardRef((props, ref) => {
/**
* Once loaded, the data for the list may be empty. Instead of
* displaying the table header with zero data rows,
- * the datagrid displays nothing or a custom empty component.
+ * the Datagrid displays the empty component.
*/
if (data == null || data.length === 0 || total === 0) {
if (empty) {
@@ -369,3 +370,5 @@ const sanitizeRestProps = props =>
.reduce((acc, key) => ({ ...acc, [key]: props[key] }), {});
Datagrid.displayName = 'Datagrid';
+
+const DefaultEmpty = ;
diff --git a/packages/ra-ui-materialui/src/list/index.ts b/packages/ra-ui-materialui/src/list/index.ts
index fba6aa18b7f..b18134056e1 100644
--- a/packages/ra-ui-materialui/src/list/index.ts
+++ b/packages/ra-ui-materialui/src/list/index.ts
@@ -8,6 +8,7 @@ export * from './List';
export * from './ListActions';
export * from './listFieldTypes';
export * from './ListGuesser';
+export * from './ListNoResults';
export * from './ListToolbar';
export * from './ListView';
export * from './pagination';
diff --git a/packages/ra-ui-materialui/src/list/pagination/Pagination.spec.tsx b/packages/ra-ui-materialui/src/list/pagination/Pagination.spec.tsx
index ff64c111a03..644316f2567 100644
--- a/packages/ra-ui-materialui/src/list/pagination/Pagination.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/pagination/Pagination.spec.tsx
@@ -21,86 +21,6 @@ describe('', () => {
hasPreviousPage: undefined,
};
- describe('no results mention', () => {
- it('should display a pagination limit when there is no result', () => {
- render(
-
-
-
-
-
- );
- expect(
- screen.queryByText('ra.navigation.no_results')
- ).not.toBeNull();
- });
-
- it('should not display a pagination limit when there are results', () => {
- render(
-
-
-
-
-
- );
- expect(screen.queryByText('ra.navigation.no_results')).toBeNull();
- });
-
- it('should display a pagination limit on an out of bounds page (more than total pages)', async () => {
- jest.spyOn(console, 'error').mockImplementationOnce(() => {});
- const setPage = jest.fn().mockReturnValue(null);
- render(
-
-
-
-
-
- );
- // mui TablePagination displays no more a warning in that case
- // Then useEffect fallbacks on a valid page
- expect(
- screen.queryByText('ra.navigation.no_results')
- ).not.toBeNull();
- });
-
- it('should display a pagination limit on an out of bounds page (less than 0)', async () => {
- jest.spyOn(console, 'error').mockImplementationOnce(() => {});
- const setPage = jest.fn().mockReturnValue(null);
- render(
-
-
-
-
-
- );
- // mui TablePagination displays no more a warning in that case
- // Then useEffect fallbacks on a valid page
- expect(
- screen.queryByText('ra.navigation.no_results')
- ).not.toBeNull();
- });
- });
-
describe('Total pagination', () => {
it('should display a next button when there are more results', () => {
render(
diff --git a/packages/ra-ui-materialui/src/list/pagination/Pagination.tsx b/packages/ra-ui-materialui/src/list/pagination/Pagination.tsx
index 4a4f4bda1b5..c8e6637d93c 100644
--- a/packages/ra-ui-materialui/src/list/pagination/Pagination.tsx
+++ b/packages/ra-ui-materialui/src/list/pagination/Pagination.tsx
@@ -17,13 +17,12 @@ import {
} from 'ra-core';
import { PaginationActions } from './PaginationActions';
-import { PaginationLimit } from './PaginationLimit';
export const Pagination: FC = memo(props => {
const {
rowsPerPageOptions = DefaultRowsPerPageOptions,
actions,
- limit = DefaultLimit,
+ limit = null,
...rest
} = props;
const {
@@ -97,7 +96,12 @@ export const Pagination: FC = memo(props => {
// Avoid rendering TablePagination if "page" value is invalid
if (total === 0 || page < 1 || (total != null && page > totalPages)) {
- return limit;
+ if (limit != null && process.env.NODE_ENV === 'development') {
+ console.warn(
+ 'The Pagination limit prop is deprecated. Empty state should be handled by the component displaying data (Datagrid, SimpleList).'
+ );
+ }
+ return null;
}
if (isSmall) {
@@ -149,7 +153,6 @@ Pagination.propTypes = {
rowsPerPageOptions: PropTypes.arrayOf(PropTypes.number),
};
-const DefaultLimit = ;
const DefaultRowsPerPageOptions = [5, 10, 25, 50];
const emptyArray = [];
diff --git a/packages/ra-ui-materialui/src/list/pagination/PaginationLimit.tsx b/packages/ra-ui-materialui/src/list/pagination/PaginationLimit.tsx
index 747c7206ead..cc8b9cf9fa3 100644
--- a/packages/ra-ui-materialui/src/list/pagination/PaginationLimit.tsx
+++ b/packages/ra-ui-materialui/src/list/pagination/PaginationLimit.tsx
@@ -4,6 +4,9 @@ import CardContent from '@mui/material/CardContent';
import Typography from '@mui/material/Typography';
import { useTranslate } from 'ra-core';
+/**
+ * @deprecated Empty state should be handled by the component displaying data (Datagrid, SimpleList).
+ */
export const PaginationLimit = memo(() => {
const translate = useTranslate();
return (