From 4ce56793497f91c977c3f68ca6e0a70112ff2b80 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Tue, 20 May 2025 16:30:59 +0200
Subject: [PATCH 01/20] update show test to display the datagrid

---
 .../src/detail/ShowGuesser.spec.tsx              | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
index 10c40540cba..1cfbd6a606e 100644
--- a/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
+++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
@@ -8,13 +8,18 @@ import { ThemeProvider } from '../theme/ThemeProvider';
 
 describe('<ShowGuesser />', () => {
     it('should log the guessed Show view based on the fetched record', async () => {
-        const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
+        const logSpy = jest
+            .spyOn(console, 'log')
+            .mockImplementation(console.warn);
         const dataProvider = {
             getOne: () =>
                 Promise.resolve({
                     data: {
                         id: 123,
-                        author: 'john doe',
+                        authors: [
+                            { id: 1, name: 'john doe', dob: '1990-01-01' },
+                            { id: 2, name: 'jane doe', dob: '1992-01-01' },
+                        ],
                         post_id: 6,
                         score: 3,
                         body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.",
@@ -22,6 +27,7 @@ describe('<ShowGuesser />', () => {
                         tags_ids: [1, 2],
                     },
                 }),
+            getMany: () => Promise.resolve({ data: [] }),
         };
         render(
             <ThemeProvider>
@@ -35,13 +41,15 @@ describe('<ShowGuesser />', () => {
         });
         expect(logSpy).toHaveBeenCalledWith(`Guessed Show:
 
-import { DateField, NumberField, ReferenceArrayField, ReferenceField, Show, SimpleShowLayout, TextField } from 'react-admin';
+import { ArrayField, Datagrid, DateField, NumberField, ReferenceArrayField, ReferenceField, Show, SimpleShowLayout, TextField } from 'react-admin';
 
 export const CommentShow = () => (
     <Show>
         <SimpleShowLayout>
             <TextField source="id" />
-            <TextField source="author" />
+            <ArrayField source="authors"><Datagrid><TextField source="id" />
+<TextField source="name" />
+<DateField source="dob" /></Datagrid></ArrayField>
             <ReferenceField source="post_id" reference="posts" />
             <NumberField source="score" />
             <TextField source="body" />

From b8c394034672df0cbb37a0eb04ab54690eafc0c2 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Tue, 20 May 2025 16:34:30 +0200
Subject: [PATCH 02/20] hide ts error in tests

---
 packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
index 1f898e5ea73..60f81ab4a85 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
@@ -10,6 +10,7 @@ describe('<ListGuesser />', () => {
     it('should log the guessed List view based on the fetched records', async () => {
         const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
         const dataProvider = testDataProvider({
+            // @ts-ignore
             getList: () =>
                 Promise.resolve({
                     data: [

From 26acc5df753ba4396a28d8ebd049efd1044d2360 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Wed, 21 May 2025 10:31:22 +0200
Subject: [PATCH 03/20] Update ListGuesser with DataTable

---
 .../ra-ui-materialui/src/list/ListGuesser.tsx |   4 +-
 .../src/list/listFieldTypes.tsx               | 101 ++++++++++--------
 2 files changed, 60 insertions(+), 45 deletions(-)

diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.tsx
index 595194e7c4f..fb57f87c7c1 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.tsx
@@ -18,11 +18,11 @@ import { listFieldTypes } from './listFieldTypes';
 import { capitalize, singularize } from 'inflection';
 
 /**
- * List component rendering a <Datagrid> based on the result of the
+ * List component rendering a <DataTable> based on the result of the
  * dataProvider.getList() call.
  *
  * The result (choice and type of columns) isn't configurable, but the
- * <ListGuesser> outputs the <Datagrid> it has guessed to the console so that
+ * <ListGuesser> outputs the <DataTable> it has guessed to the console so that
  * developers can start from there.
  *
  * To be used as the list prop of a <Resource>.
diff --git a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
index 69b1592236c..c0e01fa8489 100644
--- a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
+++ b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
@@ -1,5 +1,5 @@
 import * as React from 'react';
-import { Datagrid } from './datagrid';
+import { DataTable } from './datatable';
 import { SingleFieldList } from './SingleFieldList';
 import {
     ArrayField,
@@ -7,10 +7,8 @@ import {
     ChipField,
     DateField,
     EmailField,
-    NumberField,
     ReferenceField,
     ReferenceArrayField,
-    TextField,
     UrlField,
     ArrayFieldProps,
 } from '../field';
@@ -18,86 +16,103 @@ import {
 export const listFieldTypes = {
     table: {
         component: props => {
-            return <Datagrid {...props} />;
+            return <DataTable {...props} />;
         },
-        representation: (_props, children) => `        <Datagrid>
+        representation: (_props, children) => `        <DataTable>
 ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
-        </Datagrid>`,
+        </DataTable>`,
     },
     array: {
         component: ({ children, ...props }: ArrayFieldProps) => {
             const childrenArray = React.Children.toArray(children);
             return (
-                <ArrayField {...props}>
-                    <SingleFieldList>
-                        <ChipField
-                            source={
-                                childrenArray.length > 0 &&
-                                React.isValidElement(childrenArray[0]) &&
-                                childrenArray[0].props.source
-                            }
-                        />
-                    </SingleFieldList>
-                </ArrayField>
+                <DataTable.Col {...props}>
+                    <ArrayField {...props}>
+                        <SingleFieldList>
+                            <ChipField
+                                source={
+                                    childrenArray.length > 0 &&
+                                    React.isValidElement(childrenArray[0]) &&
+                                    childrenArray[0].props.source
+                                }
+                            />
+                        </SingleFieldList>
+                    </ArrayField>
+                </DataTable.Col>
             );
         },
         representation: (props, children) =>
-            `<ArrayField source="${
+            `<DataTable.Col source="${props.source}"><ArrayField source="${
                 props.source
             }"><SingleFieldList><ChipField source="${
                 children.length > 0 && children[0].getProps().source
-            }" /></SingleFieldList></ArrayField>`,
+            }" /></SingleFieldList></ArrayField></DataTable.Col>`,
     },
     boolean: {
-        component: BooleanField,
-        representation: props => `<BooleanField source="${props.source}" />`,
+        component: props => <DataTable.Col {...props} field={BooleanField} />,
+        representation: props =>
+            `<DataTable.Col source="${props.source}" field={BooleanField} />`,
     },
     date: {
-        component: DateField,
-        representation: props => `<DateField source="${props.source}" />`,
+        component: props => <DataTable.Col {...props} field={DateField} />,
+        representation: props =>
+            `<DataTable.Col source="${props.source}" field={DateField} />`,
     },
     email: {
-        component: EmailField,
-        representation: props => `<EmailField source="${props.source}" />`,
+        component: props => <DataTable.Col {...props} field={EmailField} />,
+        representation: props =>
+            `<DataTable.Col source="${props.source}" field={EmailField} />`,
     },
     id: {
-        component: TextField,
-        representation: props => `<TextField source="${props.source}" />`,
+        component: props => <DataTable.Col {...props} />,
+        representation: props => `<DataTable.Col source="${props.source}" />`,
     },
     number: {
-        component: NumberField,
-        representation: props => `<NumberField source="${props.source}" />`,
+        component: DataTable.NumberCol,
+        representation: props =>
+            `<DataTable.NumberCol source="${props.source}" />`,
     },
     reference: {
-        component: ReferenceField,
+        component: props => (
+            <DataTable.Col {...props}>
+                <ReferenceField {...props} />
+            </DataTable.Col>
+        ),
         representation: props =>
-            `<ReferenceField source="${props.source}" reference="${props.reference}" />`,
+            `<DataTable.Col source="${props.source}"><ReferenceField source="${props.source}" reference="${props.reference}" /></DataTable.Col>`,
     },
     referenceChild: {
-        component: () => <TextField source="id" />,
-        representation: () => `<TextField source="id" />`,
+        component: () => <DataTable.Col source="id" />,
+        representation: () => `<DataTable.Col source="id" />`,
     },
     referenceArray: {
-        component: ReferenceArrayField,
+        component: props => (
+            <DataTable.Col {...props}>
+                <ReferenceArrayField {...props} />
+            </DataTable.Col>
+        ),
         representation: props =>
-            `<ReferenceArrayField source="${props.source}" reference="${props.reference}" />`,
+            `<DataTable.Col source="${props.source}"><ReferenceArrayField source="${props.source}" reference="${props.reference}" /></DataTable.Col>`,
     },
     referenceArrayChild: {
         component: () => (
-            <SingleFieldList>
-                <ChipField source="id" />
-            </SingleFieldList>
+            <DataTable.Col>
+                <SingleFieldList>
+                    <ChipField source="id" />
+                </SingleFieldList>
+            </DataTable.Col>
         ),
         representation: () =>
-            `<SingleFieldList><ChipField source="id" /></SingleFieldList>`,
+            `<DataTable.Col><SingleFieldList><ChipField source="id" /></SingleFieldList></DataTable.Col>`,
     },
     richText: undefined, // never display a rich text field in a datagrid
     string: {
-        component: TextField,
-        representation: props => `<TextField source="${props.source}" />`,
+        component: DataTable.Col,
+        representation: props => `<DataTable.Col source="${props.source}" />`,
     },
     url: {
-        component: UrlField,
-        representation: props => `<UrlField source="${props.source}" />`,
+        component: props => <DataTable.Col {...props} field={UrlField} />,
+        representation: props =>
+            `<DataTable.Col source="${props.source}" field={UrlField} />`,
     },
 };

From 92b29679d0037c9dc80f2d784595629418aded89 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Wed, 21 May 2025 16:22:07 +0200
Subject: [PATCH 04/20] fix child types

---
 .../ra-ui-materialui/src/list/listFieldTypes.tsx  | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
index c0e01fa8489..1712fc5524e 100644
--- a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
+++ b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
@@ -11,6 +11,7 @@ import {
     ReferenceArrayField,
     UrlField,
     ArrayFieldProps,
+    TextField,
 } from '../field';
 
 export const listFieldTypes = {
@@ -82,8 +83,8 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             `<DataTable.Col source="${props.source}"><ReferenceField source="${props.source}" reference="${props.reference}" /></DataTable.Col>`,
     },
     referenceChild: {
-        component: () => <DataTable.Col source="id" />,
-        representation: () => `<DataTable.Col source="id" />`,
+        component: () => <TextField source="id" />,
+        representation: () => `<TextField source="id" />`,
     },
     referenceArray: {
         component: props => (
@@ -96,14 +97,12 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
     },
     referenceArrayChild: {
         component: () => (
-            <DataTable.Col>
-                <SingleFieldList>
-                    <ChipField source="id" />
-                </SingleFieldList>
-            </DataTable.Col>
+            <SingleFieldList>
+                <ChipField source="id" />
+            </SingleFieldList>
         ),
         representation: () =>
-            `<DataTable.Col><SingleFieldList><ChipField source="id" /></SingleFieldList></DataTable.Col>`,
+            `<SingleFieldList><ChipField source="id" /></SingleFieldList>`,
     },
     richText: undefined, // never display a rich text field in a datagrid
     string: {

From ed24389de68c18540cbc77f5c73f40db1c33cdeb Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Wed, 21 May 2025 16:22:50 +0200
Subject: [PATCH 05/20] improve the story to display every possibility of
 ListGuesser

---
 .../src/list/ListGuesser.stories.tsx          | 117 ++++++++++++++----
 1 file changed, 95 insertions(+), 22 deletions(-)

diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx
index 61c93ec4168..2a9172699f1 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx
@@ -21,6 +21,8 @@ const data = {
             price: 45.99,
             category_id: 1,
             tags_ids: [1],
+            last_update: new Date('2023-10-01').toISOString(),
+            email: 'office.jeans@myshop.com',
         },
         {
             id: 2,
@@ -28,6 +30,8 @@ const data = {
             price: 69.99,
             category_id: 1,
             tags_ids: [2, 3],
+            last_update: new Date('2023-11-01').toISOString(),
+            email: 'black.elegance.jeans@myshop.com',
         },
         {
             id: 3,
@@ -35,6 +39,8 @@ const data = {
             price: 55.99,
             category_id: 1,
             tags_ids: [2, 4],
+            last_update: new Date('2023-12-01').toISOString(),
+            email: 'slim.fit.jeans@myshop.com',
         },
         {
             id: 4,
@@ -42,6 +48,8 @@ const data = {
             price: 15.99,
             category_id: 2,
             tags_ids: [1, 4, 3],
+            last_update: new Date('2023-10-15').toISOString(),
+            email: 'basic.t.shirt@myshop.com',
         },
         {
             id: 5,
@@ -49,30 +57,93 @@ const data = {
             price: 19.99,
             category_id: 6,
             tags_ids: [1, 4, 3],
+            last_update: new Date('2023-10-15').toISOString(),
+            email: 'basic.cap@myshop.com',
         },
     ],
     categories: [
-        { id: 1, name: 'Jeans' },
-        { id: 2, name: 'T-Shirts' },
-        { id: 3, name: 'Jackets' },
-        { id: 4, name: 'Shoes' },
-        { id: 5, name: 'Accessories' },
-        { id: 6, name: 'Hats' },
-        { id: 7, name: 'Socks' },
-        { id: 8, name: 'Shirts' },
-        { id: 9, name: 'Sweaters' },
-        { id: 10, name: 'Trousers' },
-        { id: 11, name: 'Coats' },
-        { id: 12, name: 'Dresses' },
-        { id: 13, name: 'Skirts' },
-        { id: 14, name: 'Swimwear' },
-        { id: 15, name: 'Bags' },
+        {
+            id: 1,
+            name: 'Jeans',
+            alternativeName: [{ name: 'denims' }, { name: 'pants' }],
+            isVeganProduction: true,
+        },
+        {
+            id: 2,
+            name: 'T-Shirts',
+            alternativeName: [{ name: 'polo' }, { name: 'tee shirt' }],
+            isVeganProduction: false,
+        },
+        {
+            id: 3,
+            name: 'Jackets',
+            alternativeName: [{ name: 'coat' }, { name: 'blazers' }],
+            isVeganProduction: false,
+        },
+        {
+            id: 4,
+            name: 'Shoes',
+            alternativeName: [{ name: 'sneakers' }, { name: 'moccasins' }],
+            isVeganProduction: false,
+        },
+        {
+            id: 5,
+            name: 'Accessories',
+            alternativeName: [{ name: 'jewelry' }, { name: 'belts' }],
+            isVeganProduction: true,
+        },
+        {
+            id: 6,
+            name: 'Hats',
+            alternativeName: [{ name: 'caps' }, { name: 'headwear' }],
+            isVeganProduction: true,
+        },
+        {
+            id: 7,
+            name: 'Socks',
+            alternativeName: [{ name: 'stockings' }, { name: 'hosiery' }],
+            isVeganProduction: false,
+        },
+        {
+            id: 8,
+            name: 'Bags',
+            alternativeName: [{ name: 'handbags' }, { name: 'purses' }],
+            isVeganProduction: false,
+        },
+        {
+            id: 9,
+            name: 'Dresses',
+            alternativeName: [{ name: 'robes' }, { name: 'gowns' }],
+            isVeganProduction: false,
+        },
+        {
+            id: 10,
+            name: 'Skirts',
+            alternativeName: [{ name: 'tutus' }, { name: 'kilts' }],
+            isVeganProduction: false,
+        },
     ],
     tags: [
-        { id: 1, name: 'top seller' },
-        { id: 2, name: 'new' },
-        { id: 3, name: 'sale' },
-        { id: 4, name: 'promotion' },
+        {
+            id: 1,
+            name: 'top seller',
+            url: 'https://www.myshop.com/tags/top-seller',
+        },
+        {
+            id: 2,
+            name: 'new',
+            url: 'https://www.myshop.com/tags/new',
+        },
+        {
+            id: 3,
+            name: 'sale',
+            url: 'https://www.myshop.com/tags/sale',
+        },
+        {
+            id: 4,
+            name: 'promotion',
+            url: 'https://www.myshop.com/tags/promotion',
+        },
     ],
 };
 
@@ -119,6 +190,8 @@ const delayedDataProvider = fakeRestProvider(
     300
 );
 
+const ListGuesserWithProdLogs = props => <ListGuesser {...props} enableLog />;
+
 export const ManyResources = () => (
     <AdminContext
         dataProvider={delayedDataProvider}
@@ -127,17 +200,17 @@ export const ManyResources = () => (
         <AdminUI>
             <Resource
                 name="products"
-                list={ListGuesser}
+                list={ListGuesserWithProdLogs}
                 recordRepresentation="name"
             />
             <Resource
                 name="categories"
-                list={ListGuesser}
+                list={ListGuesserWithProdLogs}
                 recordRepresentation="name"
             />
             <Resource
                 name="tags"
-                list={ListGuesser}
+                list={ListGuesserWithProdLogs}
                 recordRepresentation="name"
             />
         </AdminUI>

From 96899d312d346c53ff9dfc025ab4e7a012270987 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Wed, 21 May 2025 16:53:25 +0200
Subject: [PATCH 06/20] adapt test to the new ListGuesser

---
 .../src/list/ListGuesser.spec.tsx             | 97 ++++++++++---------
 1 file changed, 51 insertions(+), 46 deletions(-)

diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
index 60f81ab4a85..0df39ca51ef 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
@@ -1,58 +1,63 @@
 import * as React from 'react';
 import expect from 'expect';
-import { render, screen, waitFor } from '@testing-library/react';
-import { CoreAdminContext, testDataProvider } from 'ra-core';
-
-import { ListGuesser } from './ListGuesser';
-import { ThemeProvider } from '../theme/ThemeProvider';
+import { fireEvent, render, screen } from '@testing-library/react';
+import { ManyResources } from './ListGuesser.stories';
 
 describe('<ListGuesser />', () => {
-    it('should log the guessed List view based on the fetched records', async () => {
+    it('should log the guessed List views based on the fetched records', async () => {
         const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
-        const dataProvider = testDataProvider({
-            // @ts-ignore
-            getList: () =>
-                Promise.resolve({
-                    data: [
-                        {
-                            id: 123,
-                            author: 'john doe',
-                            post_id: 6,
-                            score: 3,
-                            body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.",
-                            created_at: new Date('2012-08-02'),
-                            tags_ids: [1, 2],
-                        },
-                    ],
-                    total: 1,
-                }),
-            getMany: () => Promise.resolve({ data: [], total: 0 }),
-        });
-        render(
-            <ThemeProvider>
-                <CoreAdminContext dataProvider={dataProvider as any}>
-                    <ListGuesser resource="comments" enableLog />
-                </CoreAdminContext>
-            </ThemeProvider>
-        );
-        await waitFor(() => {
-            screen.getByText('john doe');
-        });
+        render(<ManyResources />);
+        await screen.findAllByText('top seller');
+        expect(logSpy).toHaveBeenCalledWith(`Guessed List:
+
+import { DataTable, DataTable.Col, DataTable.NumberCol, List, ReferenceArrayField, ReferenceField } from 'react-admin';
+
+export const ProductList = () => (
+    <List>
+        <DataTable>
+            <DataTable.Col source="id" />
+            <DataTable.Col source="name" />
+            <DataTable.NumberCol source="price" />
+            <DataTable.Col source="category_id"><ReferenceField source="category_id" reference="categories" /></DataTable.Col>
+            <DataTable.Col source="tags_ids"><ReferenceArrayField source="tags_ids" reference="tags" /></DataTable.Col>
+            <DataTable.Col source="last_update" field={DateField} />
+            <DataTable.Col source="email" field={EmailField} />
+        </DataTable>
+    </List>
+);`);
+        logSpy.mockClear();
+
+        fireEvent.click(screen.getByText('Categories'));
+        await screen.findByText('Jeans');
+        expect(logSpy).toHaveBeenCalledWith(`Guessed List:
+
+import { ArrayField, ChipField, DataTable, DataTable.Col, List, SingleFieldList } from 'react-admin';
+
+export const CategoryList = () => (
+    <List>
+        <DataTable>
+            <DataTable.Col source="id" />
+            <DataTable.Col source="name" />
+            <DataTable.Col source="alternativeName"><ArrayField source="alternativeName"><SingleFieldList><ChipField source="name" /></SingleFieldList></ArrayField></DataTable.Col>
+            <DataTable.Col source="isVeganProduction" field={BooleanField} />
+        </DataTable>
+    </List>
+);`);
+
+        logSpy.mockClear();
+        fireEvent.click(screen.getByText('Tags'));
+        await screen.findByText('top seller');
         expect(logSpy).toHaveBeenCalledWith(`Guessed List:
 
-import { Datagrid, DateField, List, NumberField, ReferenceArrayField, ReferenceField, TextField } from 'react-admin';
+import { DataTable, DataTable.Col, List } from 'react-admin';
 
-export const CommentList = () => (
+export const TagList = () => (
     <List>
-        <Datagrid>
-            <TextField source="id" />
-            <TextField source="author" />
-            <ReferenceField source="post_id" reference="posts" />
-            <NumberField source="score" />
-            <TextField source="body" />
-            <DateField source="created_at" />
-            <ReferenceArrayField source="tags_ids" reference="tags" />
-        </Datagrid>
+        <DataTable>
+            <DataTable.Col source="id" />
+            <DataTable.Col source="name" />
+            <DataTable.Col source="url" field={UrlField} />
+        </DataTable>
     </List>
 );`);
     });

From ea937454ab0b0f5d781661162933c2bcf5bba064 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Mon, 26 May 2025 10:44:56 +0200
Subject: [PATCH 07/20] make the representation prettier with indentation + use
 children instead of field prop

---
 .../src/list/listFieldTypes.tsx               | 64 ++++++++++++++-----
 1 file changed, 48 insertions(+), 16 deletions(-)

diff --git a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
index 1712fc5524e..670ed800e7a 100644
--- a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
+++ b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
@@ -43,26 +43,46 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             );
         },
         representation: (props, children) =>
-            `<DataTable.Col source="${props.source}"><ArrayField source="${
-                props.source
-            }"><SingleFieldList><ChipField source="${
-                children.length > 0 && children[0].getProps().source
-            }" /></SingleFieldList></ArrayField></DataTable.Col>`,
+            `<DataTable.Col source="${props.source}">
+                <ArrayField source="${props.source}">
+                    <SingleFieldList>
+                        <ChipField source="${children.length > 0 && children[0].getProps().source}" />
+                    </SingleFieldList>
+                </ArrayField>
+            </DataTable.Col>`,
     },
     boolean: {
-        component: props => <DataTable.Col {...props} field={BooleanField} />,
+        component: props => (
+            <DataTable.Col {...props}>
+                <BooleanField {...props} />
+            </DataTable.Col>
+        ),
         representation: props =>
-            `<DataTable.Col source="${props.source}" field={BooleanField} />`,
+            `<DataTable.Col source="${props.source}" />
+                <BooleanField source="${props.source}" />
+            </DataTable.Col>`,
     },
     date: {
-        component: props => <DataTable.Col {...props} field={DateField} />,
+        component: props => (
+            <DataTable.Col {...props}>
+                <DateField {...props} />
+            </DataTable.Col>
+        ),
         representation: props =>
-            `<DataTable.Col source="${props.source}" field={DateField} />`,
+            `<DataTable.Col source="${props.source}" />
+                <DateField source="${props.source}" />
+            </DataTable.Col>`,
     },
     email: {
-        component: props => <DataTable.Col {...props} field={EmailField} />,
+        component: props => (
+            <DataTable.Col {...props}>
+                <EmailField {...props} />
+            </DataTable.Col>
+        ),
         representation: props =>
-            `<DataTable.Col source="${props.source}" field={EmailField} />`,
+            `<DataTable.Col source="${props.source}" />
+                <EmailField source="${props.source}" />
+            </DataTable.Col>`,
     },
     id: {
         component: props => <DataTable.Col {...props} />,
@@ -80,7 +100,9 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             </DataTable.Col>
         ),
         representation: props =>
-            `<DataTable.Col source="${props.source}"><ReferenceField source="${props.source}" reference="${props.reference}" /></DataTable.Col>`,
+            `<DataTable.Col source="${props.source}">
+                <ReferenceField source="${props.source}" reference="${props.reference}" />
+            </DataTable.Col>`,
     },
     referenceChild: {
         component: () => <TextField source="id" />,
@@ -93,7 +115,9 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             </DataTable.Col>
         ),
         representation: props =>
-            `<DataTable.Col source="${props.source}"><ReferenceArrayField source="${props.source}" reference="${props.reference}" /></DataTable.Col>`,
+            `<DataTable.Col source="${props.source}">
+                <ReferenceArrayField source="${props.source}" reference="${props.reference}" />
+            </DataTable.Col>`,
     },
     referenceArrayChild: {
         component: () => (
@@ -102,7 +126,9 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             </SingleFieldList>
         ),
         representation: () =>
-            `<SingleFieldList><ChipField source="id" /></SingleFieldList>`,
+            `<SingleFieldList>
+                <ChipField source="id" />
+            </SingleFieldList>`,
     },
     richText: undefined, // never display a rich text field in a datagrid
     string: {
@@ -110,8 +136,14 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
         representation: props => `<DataTable.Col source="${props.source}" />`,
     },
     url: {
-        component: props => <DataTable.Col {...props} field={UrlField} />,
+        component: props => (
+            <DataTable.Col {...props}>
+                <UrlField {...props} />
+            </DataTable.Col>
+        ),
         representation: props =>
-            `<DataTable.Col source="${props.source}" field={UrlField} />`,
+            `<DataTable.Col source="${props.source}" />
+                <UrlField source="${props.source}" />
+            </DataTable.Col>`,
     },
 };

From 46dd322aeb82712f13c0ef68ba9d3e2e64c9341f Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Mon, 26 May 2025 10:57:39 +0200
Subject: [PATCH 08/20] fix representation

---
 packages/ra-ui-materialui/src/list/listFieldTypes.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
index 670ed800e7a..dcb4745ff10 100644
--- a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
+++ b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
@@ -58,7 +58,7 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             </DataTable.Col>
         ),
         representation: props =>
-            `<DataTable.Col source="${props.source}" />
+            `<DataTable.Col source="${props.source}">
                 <BooleanField source="${props.source}" />
             </DataTable.Col>`,
     },
@@ -69,7 +69,7 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             </DataTable.Col>
         ),
         representation: props =>
-            `<DataTable.Col source="${props.source}" />
+            `<DataTable.Col source="${props.source}">
                 <DateField source="${props.source}" />
             </DataTable.Col>`,
     },
@@ -80,7 +80,7 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             </DataTable.Col>
         ),
         representation: props =>
-            `<DataTable.Col source="${props.source}" />
+            `<DataTable.Col source="${props.source}">
                 <EmailField source="${props.source}" />
             </DataTable.Col>`,
     },
@@ -142,7 +142,7 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             </DataTable.Col>
         ),
         representation: props =>
-            `<DataTable.Col source="${props.source}" />
+            `<DataTable.Col source="${props.source}">
                 <UrlField source="${props.source}" />
             </DataTable.Col>`,
     },

From 51dc3cf55947781bd20e98afc8a7be6b85f2b1ea Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Mon, 26 May 2025 10:57:55 +0200
Subject: [PATCH 09/20] adapt tests to this new representation

---
 .../src/list/ListGuesser.spec.tsx             | 40 ++++++++++++++-----
 1 file changed, 29 insertions(+), 11 deletions(-)

diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
index 0df39ca51ef..7fb035e51b6 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
@@ -7,10 +7,10 @@ describe('<ListGuesser />', () => {
     it('should log the guessed List views based on the fetched records', async () => {
         const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
         render(<ManyResources />);
-        await screen.findAllByText('top seller');
+        await screen.findAllByText('top seller', undefined, { timeout: 2000 });
         expect(logSpy).toHaveBeenCalledWith(`Guessed List:
 
-import { DataTable, DataTable.Col, DataTable.NumberCol, List, ReferenceArrayField, ReferenceField } from 'react-admin';
+import { DataTable, DataTable.Col, DataTable.NumberCol, DateField, EmailField, List, ReferenceArrayField, ReferenceField } from 'react-admin';
 
 export const ProductList = () => (
     <List>
@@ -18,10 +18,18 @@ export const ProductList = () => (
             <DataTable.Col source="id" />
             <DataTable.Col source="name" />
             <DataTable.NumberCol source="price" />
-            <DataTable.Col source="category_id"><ReferenceField source="category_id" reference="categories" /></DataTable.Col>
-            <DataTable.Col source="tags_ids"><ReferenceArrayField source="tags_ids" reference="tags" /></DataTable.Col>
-            <DataTable.Col source="last_update" field={DateField} />
-            <DataTable.Col source="email" field={EmailField} />
+            <DataTable.Col source="category_id">
+                <ReferenceField source="category_id" reference="categories" />
+            </DataTable.Col>
+            <DataTable.Col source="tags_ids">
+                <ReferenceArrayField source="tags_ids" reference="tags" />
+            </DataTable.Col>
+            <DataTable.Col source="last_update">
+                <DateField source="last_update" />
+            </DataTable.Col>
+            <DataTable.Col source="email">
+                <EmailField source="email" />
+            </DataTable.Col>
         </DataTable>
     </List>
 );`);
@@ -31,15 +39,23 @@ export const ProductList = () => (
         await screen.findByText('Jeans');
         expect(logSpy).toHaveBeenCalledWith(`Guessed List:
 
-import { ArrayField, ChipField, DataTable, DataTable.Col, List, SingleFieldList } from 'react-admin';
+import { ArrayField, BooleanField, ChipField, DataTable, DataTable.Col, List, SingleFieldList } from 'react-admin';
 
 export const CategoryList = () => (
     <List>
         <DataTable>
             <DataTable.Col source="id" />
             <DataTable.Col source="name" />
-            <DataTable.Col source="alternativeName"><ArrayField source="alternativeName"><SingleFieldList><ChipField source="name" /></SingleFieldList></ArrayField></DataTable.Col>
-            <DataTable.Col source="isVeganProduction" field={BooleanField} />
+            <DataTable.Col source="alternativeName">
+                <ArrayField source="alternativeName">
+                    <SingleFieldList>
+                        <ChipField source="name" />
+                    </SingleFieldList>
+                </ArrayField>
+            </DataTable.Col>
+            <DataTable.Col source="isVeganProduction">
+                <BooleanField source="isVeganProduction" />
+            </DataTable.Col>
         </DataTable>
     </List>
 );`);
@@ -49,14 +65,16 @@ export const CategoryList = () => (
         await screen.findByText('top seller');
         expect(logSpy).toHaveBeenCalledWith(`Guessed List:
 
-import { DataTable, DataTable.Col, List } from 'react-admin';
+import { DataTable, DataTable.Col, List, UrlField } from 'react-admin';
 
 export const TagList = () => (
     <List>
         <DataTable>
             <DataTable.Col source="id" />
             <DataTable.Col source="name" />
-            <DataTable.Col source="url" field={UrlField} />
+            <DataTable.Col source="url">
+                <UrlField source="url" />
+            </DataTable.Col>
         </DataTable>
     </List>
 );`);

From 6479209afdd890cb071c1d1855e816d26d29bb0b Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Mon, 26 May 2025 11:03:28 +0200
Subject: [PATCH 10/20] Do not import `DataTable.Col` or `DataTable.NumberCol`

---
 packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx | 6 +++---
 packages/ra-ui-materialui/src/list/ListGuesser.tsx      | 7 ++++++-
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
index 7fb035e51b6..0c8f0fcd04c 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
@@ -10,7 +10,7 @@ describe('<ListGuesser />', () => {
         await screen.findAllByText('top seller', undefined, { timeout: 2000 });
         expect(logSpy).toHaveBeenCalledWith(`Guessed List:
 
-import { DataTable, DataTable.Col, DataTable.NumberCol, DateField, EmailField, List, ReferenceArrayField, ReferenceField } from 'react-admin';
+import { DataTable, DateField, EmailField, List, ReferenceArrayField, ReferenceField } from 'react-admin';
 
 export const ProductList = () => (
     <List>
@@ -39,7 +39,7 @@ export const ProductList = () => (
         await screen.findByText('Jeans');
         expect(logSpy).toHaveBeenCalledWith(`Guessed List:
 
-import { ArrayField, BooleanField, ChipField, DataTable, DataTable.Col, List, SingleFieldList } from 'react-admin';
+import { ArrayField, BooleanField, ChipField, DataTable, List, SingleFieldList } from 'react-admin';
 
 export const CategoryList = () => (
     <List>
@@ -65,7 +65,7 @@ export const CategoryList = () => (
         await screen.findByText('top seller');
         expect(logSpy).toHaveBeenCalledWith(`Guessed List:
 
-import { DataTable, DataTable.Col, List, UrlField } from 'react-admin';
+import { DataTable, List, UrlField } from 'react-admin';
 
 export const TagList = () => (
     <List>
diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.tsx
index fb57f87c7c1..bb69d4e718c 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.tsx
@@ -132,10 +132,15 @@ const ListViewGuesser = (
                 .sort();
 
             if (enableLog) {
+                const importsToLog = components.includes('DataTable')
+                    ? components.filter(
+                          component => !component.startsWith('DataTable.')
+                      )
+                    : components;
                 console.log(
                     `Guessed List:
 
-import { ${components.join(', ')} } from 'react-admin';
+import { ${importsToLog.join(', ')} } from 'react-admin';
 
 export const ${capitalize(singularize(resource))}List = () => (
     <List>

From f1d9dab7759b0a0b3236a42c4ef66cc783408664 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Mon, 26 May 2025 11:50:55 +0200
Subject: [PATCH 11/20] use DataTable in Tutorial instead of Datagrid

---
 docs/Tutorial.md | 231 +++++++++++++++++++++++++----------------------
 1 file changed, 124 insertions(+), 107 deletions(-)

diff --git a/docs/Tutorial.md b/docs/Tutorial.md
index 4b45b42960f..8cba51b5a80 100644
--- a/docs/Tutorial.md
+++ b/docs/Tutorial.md
@@ -26,9 +26,9 @@ The final result is a web application that allows you to list, create, edit, and
 React-admin is built on React. To start, we'll use [create-react-admin](./CreateReactAdmin.md) to bootstrap a new web application:
 
 ```sh
-npm create react-admin@latest test-admin
+npm create react-admin@latest test-admin -- --interactive
 # or
-yarn create react-admin@latest test-admin
+yarn create react-admin test-admin --interactive
 ```
 
 When prompted, choose **JSON Server** as the data provider, then **None** as the auth provider. Do not add any resources for now and press **Enter**. Next, choose either `npm` or `yarn` and press **Enter**. Once everything is installed, run the following commands:
@@ -168,20 +168,20 @@ Copy this code and create a new `UserList` component in a new file called `users
 
 ```tsx
 // in src/users.tsx
-import { List, Datagrid, TextField, EmailField } from "react-admin";
+import { List, DataTable, EmailField } from "react-admin";
 
 export const UserList = () => (
     <List>
-        <Datagrid>
-            <TextField source="id" />
-            <TextField source="name" />
-            <TextField source="username" />
-            <EmailField source="email" />
-            <TextField source="address.street" />
-            <TextField source="phone" />
-            <TextField source="website" />
-            <TextField source="company.name" />
-        </Datagrid>
+        <DataTable>
+            <DataTable.Col source="id" />
+            <DataTable.Col source="name" />
+            <DataTable.Col source="username" />
+            <DataTable.Col source="email" field={EmailField} />
+            <DataTable.Col source="address.street" />
+            <DataTable.Col source="phone" />
+            <DataTable.Col source="website" />
+            <DataTable.Col source="company.name" />
+        </DataTable>
     </List>
 );
 ```
@@ -214,16 +214,16 @@ Let's take a closer look at the `<UserList>` component:
 ```tsx
 export const UserList = () => (
     <List>
-        <Datagrid>
-            <TextField source="id" />
-            <TextField source="name" />
-            <TextField source="username" />
-            <EmailField source="email" />
-            <TextField source="address.street" />
-            <TextField source="phone" />
-            <TextField source="website" />
-            <TextField source="company.name" />
-        </Datagrid>
+        <DataTable>
+            <DataTable.Col source="id" />
+            <DataTable.Col source="name" />
+            <DataTable.Col source="username" />
+            <DataTable.Col source="email" field={EmailField} />
+            <DataTable.Col source="address.street" />
+            <DataTable.Col source="phone" />
+            <DataTable.Col source="website" />
+            <DataTable.Col source="company.name" />
+        </DataTable>
     </List>
 );
 ```
@@ -238,7 +238,7 @@ The root component, [`<List>`](./List.md), reads the query parameters, fetches d
 
 This demonstrates the goal of react-admin: helping developers build sophisticated applications with simple syntax.
 
-In most frameworks, "simple" often implies limited capabilities, making it challenging to extend beyond basic features. React-admin addresses this through *composition*. `<List>` handles data fetching, while rendering is delegated to its child—in this case, [`<Datagrid>`](./Datagrid.md).  Essentially, the code composes the functionalities of `<List>` and `<Datagrid>` functionalities.
+In most frameworks, "simple" often implies limited capabilities, making it challenging to extend beyond basic features. React-admin addresses this through *composition*. `<List>` handles data fetching, while rendering is delegated to its child—in this case, [`<DataTable>`](./DataTable.md).  Essentially, the code composes the functionalities of `<List>` and `<DataTable>` functionalities.
 
 This means we can compose `<List>` with another component - for instance [`<SimpleList>`](./SimpleList.md):
 
@@ -275,12 +275,12 @@ React-admin's layout is responsive by default. Try resizing your browser, and yo
   Your browser does not support the video tag.
 </video>
 
-However, `<SimpleList>` has low information density on desktop. Let's modify `<UserList>` to use `<Datagrid>` on larger screens and `<SimpleList>` on smaller screens. We can achieve this using [Material UI's `useMediaQuery` hook](https://mui.com/material-ui/react-use-media-query/):
+However, `<SimpleList>` has low information density on desktop. Let's modify `<UserList>` to use `<DataTable>` on larger screens and `<SimpleList>` on smaller screens. We can achieve this using [Material UI's `useMediaQuery` hook](https://mui.com/material-ui/react-use-media-query/):
 
 ```tsx
 // in src/users.tsx
 import { useMediaQuery, Theme } from "@mui/material";
-import { List, SimpleList, Datagrid, TextField, EmailField } from "react-admin";
+import { List, SimpleList, DataTable, EmailField } from "react-admin";
 
 export const UserList = () => {
     const isSmall = useMediaQuery<Theme>((theme) => theme.breakpoints.down("sm"));
@@ -293,16 +293,16 @@ export const UserList = () => {
                     tertiaryText={(record) => record.email}
                 />
             ) : (
-                <Datagrid>
-                    <TextField source="id" />
-                    <TextField source="name" />
-                    <TextField source="username" />
-                    <EmailField source="email" />
-                    <TextField source="address.street" />
-                    <TextField source="phone" />
-                    <TextField source="website" />
-                    <TextField source="company.name" />
-                </Datagrid>
+                <DataTable>
+                    <DataTable.Col source="id" />
+                    <DataTable.Col source="name" />
+                    <DataTable.Col source="username" />
+                    <DataTable.Col source="email" field={EmailField} />
+                    <DataTable.Col source="address.street" />
+                    <DataTable.Col source="phone" />
+                    <DataTable.Col source="website" />
+                    <DataTable.Col source="company.name" />
+                </DataTable>
             )}
         </List>
     );
@@ -321,22 +321,25 @@ The `<List>` component's child can be anything—even a custom component with it
 
 ## Selecting Columns
 
-Let's get back to `<Datagrid>`. It reads the data fetched by `<List>`, then renders a table with one row for each record. `<Datagrid>` uses its child components (here, a list of [Field component](./Fields.md)) to render the columns. Each Field component renders one field of the current record, specified by the `source` prop.
+Let's get back to `<DataTable>`.
+It reads the data fetched by `<List>`, then renders a table with one row for each record. `<DataTable>` uses its child components (here, a list of [Field component](./Fields.md)) to render the columns.
+Each `<DataTable.Col>` component renders one field of the current record, specified by the `source` prop.
 
-`<ListGuesser>` created one column for every field in the API response. That's a bit too much for a usable grid, so let's remove a couple of `<TextField>` components from the Datagrid and see the effect:
+`<ListGuesser>` created one column for every field in the API response.
+That's a bit too much for a usable grid, so let's remove a couple of `<DataTable.Col>` components from the DataTable and see the effect:
 
 ```diff
 // in src/users.tsx
-  <Datagrid>
-    <TextField source="id" />
-    <TextField source="name" />
--   <TextField source="username" />
-    <EmailField source="email" />
--   <TextField source="address.street" />
-    <TextField source="phone" />
-    <TextField source="website" />
-    <TextField source="company.name" />
-  </Datagrid>
+  <DataTable>
+    <DataTable.Col source="id" />
+    <DataTable.Col source="name" />
+-   <DataTable.Col source="username" />
+    <DataTable.Col source="email" field={EmailField} />
+-   <DataTable.Col source="address.street" />
+    <DataTable.Col source="phone" />
+    <DataTable.Col source="website" />
+    <DataTable.Col source="company.name" />
+  </DataTable>
 ```
 
 [![Users List](./img/tutorial_users_list_selected_columns.png)](./img/tutorial_users_list_selected_columns.png)
@@ -345,24 +348,25 @@ In react-admin, most configuration is done through components. Instead of using
 
 ## Using Field Types
 
-So far, you've used [`<TextField>`](./TextField.md) and [`<EmailField>`](./EmailField.md). React-admin provides [many more Field components](./Fields.md) to handle different data types—numbers, dates, images, arrays, and more.
+So far, you've used simples [`<DataTable.Col>`](.//DataTable.md#datatablecol) and [`EmailField`](./EmailField.md) as [a `DataTable.Col` `field`](./DataTable.md#field).
+React-admin provides [many more Field components](./Fields.md) to handle different data types—numbers, dates, images, arrays, and more.
 
 For instance, instead of displaying the `website` field as plain text, you could make it a clickable link using [`<UrlField>`](./UrlField.md):
 
 ```diff
 // in src/users.tsx
--import { List, SimpleList, Datagrid, TextField, EmailField } from "react-admin";
-+import { List, SimpleList, Datagrid, TextField, EmailField, UrlField } from "react-admin";
+-import { List, SimpleList, DataTable, EmailField } from "react-admin";
++import { List, SimpleList, DataTable, EmailField, UrlField } from "react-admin";
 // ...
-  <Datagrid>
-    <TextField source="id" />
-    <TextField source="name" />
-    <EmailField source="email" />
-    <TextField source="phone" />
--   <TextField source="website" />
-+   <UrlField source="website" />
-    <TextField source="company.name" />
-  </Datagrid>
+  <DataTable>
+    <DataTable.Col source="id" />
+    <DataTable.Col source="name" />
+    <DataTable.Col source="email" field={EmailField} />
+    <DataTable.Col source="phone" />
+-   <DataTable.Col source="website" />
++   <DataTable.Col source="website" field={UrlField} />
+    <DataTable.Col source="company.name" />
+  </DataTable>
 ```
 
 [![Url Field](./img/tutorial_url_field.png)](./img/tutorial_url_field.png)
@@ -371,9 +375,11 @@ This is typical of the early stages of development with react-admin: use a guess
 
 ## Writing A Custom Field
 
-In react-admin, fields are just React components. When rendered, they grab the `record` fetched from the API (e.g. `{ "id": 2, "name": "Ervin Howell", "website": "anastasia.net", ... }`) using a custom hook, and use the `source` prop (e.g. `website`) to get the value they should display (e.g. "anastasia.net").
+In react-admin, fields are just React components.
+When rendered, they grab the `record` fetched from the API (e.g. `{ "id": 2, "name": "Ervin Howell", "website": "anastasia.net", ... }`) using a custom hook, and use the `source` prop (e.g. `website`) to get the value they should display (e.g. "anastasia.net").
 
-That means you can do the same to [write a custom field](./Fields.md#writing-your-own-field-component). For instance, here is a simplified version of the `<UrlField>`:
+That means you can do the same to [write a custom field](./Fields.md#writing-your-own-field-component).
+For instance, here is a simplified version of the `<UrlField>`:
 
 ```tsx
 // in src/MyUrlField.tsx
@@ -388,25 +394,27 @@ const MyUrlField = ({ source }: { source: string }) => {
 export default MyUrlField;
 ```
 
-For each row, `<Datagrid>` creates a `RecordContext` and stores the current record in it. [`useRecordContext`](./useRecordContext.md) allows you to read that record. It's one of the 50+ headless hooks that react-admin exposes to let you build your own components without forcing a particular UI.
+For each row, `<DataTable>` creates a `RecordContext` and stores the current record in it.
+[`useRecordContext`](./useRecordContext.md) allows you to read that record.
+It's one of the 50+ headless hooks that react-admin exposes to let you build your own components without forcing a particular UI.
 
 You can use the `<MyUrlField>` component in `<UserList>` instead of react-admin's `<UrlField>` component, and it will work just the same.
 
 ```diff
 // in src/users.tsx
--import { List, SimpleList, Datagrid, TextField, EmailField, UrlField } from "react-admin";
-+import { List, SimpleList, Datagrid, TextField, EmailField } from "react-admin";
+-import { List, SimpleList, DataTable, EmailField, UrlField } from "react-admin";
++import { List, SimpleList, DataTable, EmailField } from "react-admin";
 +import MyUrlField from './MyUrlField';
 // ...
-  <Datagrid>
-    <TextField source="id" />
-    <TextField source="name" />
-    <EmailField source="email" />
-    <TextField source="phone" />
--   <UrlField source="website" />
-+   <MyUrlField source="website" />
-    <TextField source="company.name" />
-  </Datagrid>
+  <DataTable>
+    <DataTable.Col source="id" />
+    <DataTable.Col source="name" />
+    <DataTable.Col source="email" source={EmailField} />
+    <DataTable.Col source="phone" />
+-   <DataTable.Col source="website" source={UrlField} />
++   <DataTable.Col source="website" source={MyUrlField} />
+    <DataTable.Col source="company.name" />
+  </DataTable>
 ```
 
 This means react-admin never blocks you: if one react-admin component doesn't perfectly suit your needs, you can just swap it with your own version.
@@ -478,20 +486,23 @@ export const App = () => (
 
 [![Guessed Post List](./img/tutorial_guessed_post_list.png)](./img/tutorial_guessed_post_list.png)
 
-The `ListGuesser` suggests using a [`<ReferenceField>`](./ReferenceField.md) for the `userId` field. Let's play with this new field by creating the `PostList` component based on the code dumped by the guesser:
+The `ListGuesser` suggests using a [`<ReferenceField>`](./ReferenceField.md) for the `userId` field.
+Let's play with this new field by creating the `PostList` component based on the code dumped by the guesser:
 
 ```tsx
 // in src/posts.tsx
-import { List, Datagrid, TextField, ReferenceField } from "react-admin";
+import { List, DataTable, ReferenceField } from "react-admin";
 
 export const PostList = () => (
     <List>
-        <Datagrid>
-            <ReferenceField source="userId" reference="users" />
-            <TextField source="id" />
-            <TextField source="title" />
-            <TextField source="body" />
-        </Datagrid>
+        <DataTable>
+            <DataTable.Col source="userId">
+                <ReferenceField source="userId" reference="users" />
+            </DataTable.Col>
+            <DataTable.Col source="id" />
+            <DataTable.Col source="title" />
+            <DataTable.Col source="body" />
+        </DataTable>
     </List>
 );
 ```
@@ -521,26 +532,32 @@ When displaying the posts list, react-admin is smart enough to display the `name
 
 The `<ReferenceField>` component fetches the reference data, creates a `RecordContext` with the result, and renders the record representation (or its children).
 
-**Tip**: Look at the network tab of your browser again: react-admin deduplicates requests for users and aggregates them in order to make only *one* HTTP request to the `/users` endpoint for the whole Datagrid. That's one of many optimizations that keep the UI fast and responsive.
+**Tip**: Look at the network tab of your browser again: react-admin deduplicates requests for users and aggregates them in order to make only *one* HTTP request to the `/users` endpoint for the whole DataTable. That's one of many optimizations that keep the UI fast and responsive.
 
-To finish the post list, place the post `id` field as the first column, and remove the `body` field. From a UX point of view, fields containing large chunks of text should not appear in a Datagrid, only in detail views. Also, to make the Edit action stand out, let's replace the default `rowClick` action with an explicit action button:
+To finish the post list, place the post `id` field as the first column, and remove the `body` field.
+From a UX point of view, fields containing large chunks of text should not appear in a DataTable, only in detail views.
+Also, to make the Edit action stand out, let's replace the default `rowClick` action with an explicit action button:
 
 ```diff
 // in src/posts.tsx
--import { List, Datagrid, TextField, ReferenceField } from "react-admin";
-+import { List, Datagrid, TextField, ReferenceField, EditButton } from "react-admin";
+-import { List, DataTable, ReferenceField } from "react-admin";
++import { List, DataTable, ReferenceField, EditButton } from "react-admin";
 
 export const PostList = () => (
   <List>
--   <Datagrid>
-+   <Datagrid rowClick={false}>
-+     <TextField source="id" />
-      <ReferenceField source="userId" reference="users" />
--     <TextField source="id" />
-      <TextField source="title" />
--     <TextField source="body" />
-+     <EditButton />
-    </Datagrid>
+-   <DataTable>
++   <DataTable rowClick={false}>
++     <DataTable.Col source="id" />
+      <DataTable.Col source="userId">
+        <ReferenceField source="userId" reference="users" />
+      </DataTable.Col source="userId">
+-     <DataTable.Col source="id" />
+      <DataTable.Col source="title" />
+-     <DataTable.Col source="body" />
++     <DataTable.Col>
++       <EditButton />
++     </DataTable.Col>
+    </DataTable>
   </List>
 );
 ```
@@ -583,13 +600,15 @@ Now that the `users` resource has a `show` view, you can also link to it from th
 // in src/posts.tsx
 export const PostList = () => (
     <List>
-        <Datagrid>
--           <ReferenceField source="userId" reference="users" />
-+           <ReferenceField source="userId" reference="users" link="show" />
-            <TextField source="id" />
-            <TextField source="title" />
-            <TextField source="body" />
-        </Datagrid>
+        <DataTable>
+            <DataTable.Col>
+-             <ReferenceField source="userId" reference="users" />
++             <ReferenceField source="userId" reference="users" link="show" />
+            </DataTable.Col>
+            <DataTable.Col source="id" />
+            <DataTable.Col source="title" />
+            <DataTable.Col source="body" />
+        </DataTable>
     </List>
 );
 ```
@@ -633,8 +652,7 @@ Copy the `<PostEdit>` code dumped by the guesser in the console to the `posts.ts
 // in src/posts.tsx
 import {
     List,
-    Datagrid,
-    TextField,
+    DataTable,
     ReferenceField,
     EditButton,
     Edit,
@@ -699,7 +717,7 @@ export const PostEdit = () => (
 ```
 {% endraw %}
 
-If you've understood the `<List>` component, the `<Edit>` component will be no surprise. It's responsible for fetching the record and displaying the page title. It passes the record down to the [`<SimpleForm>`](./SimpleForm.md) component, which is responsible for the form layout, default values, and validation. Just like `<Datagrid>`, `<SimpleForm>` uses its children to determine the form inputs to display. It expects [*input components*](./Inputs.md) as children. [`<TextInput>`](./TextInput.md) and [`<ReferenceInput>`](./ReferenceInput.md) are such inputs.
+If you've understood the `<List>` component, the `<Edit>` component will be no surprise. It's responsible for fetching the record and displaying the page title. It passes the record down to the [`<SimpleForm>`](./SimpleForm.md) component, which is responsible for the form layout, default values, and validation. Just like `<DataTable>`, `<SimpleForm>` uses its children to determine the form inputs to display. It expects [*input components*](./Inputs.md) as children. [`<TextInput>`](./TextInput.md) and [`<ReferenceInput>`](./ReferenceInput.md) are such inputs.
 
 The `<ReferenceInput>` takes the same props as the `<ReferenceField>` (used earlier in the `<PostList>` page). `<ReferenceInput>` uses these props to fetch the API for possible references related to the current record (in this case, possible `users` for the current `post`). It then creates a context with the possible choices and renders an [`<AutocompleteInput>`](./AutocompleteInput.md), which is responsible for displaying the choices and letting the user select one.
 
@@ -711,8 +729,7 @@ Let's allow users to create posts, too. Copy the `<PostEdit>` component into a `
 // in src/posts.tsx
 import {
     List,
-    Datagrid,
-    TextField,
+    DataTable,
     ReferenceField,
     EditButton,
     Edit,

From 2431f0f79b1b74ccb946270ac7e90867796c1551 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Mon, 26 May 2025 14:55:06 +0200
Subject: [PATCH 12/20] make representation prettier

---
 packages/ra-ui-materialui/src/detail/showFieldTypes.tsx | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx b/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
index 7e39fdc5996..2ad60289748 100644
--- a/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
+++ b/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
@@ -39,9 +39,11 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             </ArrayField>
         ),
         representation: (props: InputProps, children: InferredElement[]) =>
-            `<ArrayField source="${props.source}"><Datagrid>${children
-                .map(child => child.getRepresentation())
-                .join('\n')}</Datagrid></ArrayField>`,
+            `<ArrayField source="${props.source}">
+                <Datagrid>
+                    ${children.map(child => child.getRepresentation()).join('\n                    ')}
+                </Datagrid>
+            </ArrayField>`,
     },
     boolean: {
         component: BooleanField,

From d09f190b9d0a392bfc84b299c38844531c4d530c Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Mon, 26 May 2025 15:00:43 +0200
Subject: [PATCH 13/20] Add story + adapt test

---
 .../src/detail/ShowGuesser.spec.tsx           | 58 ++++++-------------
 .../src/detail/ShowGuesser.stories.tsx        | 48 +++++++++++++++
 .../src/list/ListGuesser.stories.tsx          |  2 +-
 3 files changed, 67 insertions(+), 41 deletions(-)
 create mode 100644 packages/ra-ui-materialui/src/detail/ShowGuesser.stories.tsx

diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
index 1cfbd6a606e..b23bcc43179 100644
--- a/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
+++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
@@ -1,60 +1,38 @@
 import * as React from 'react';
 import expect from 'expect';
-import { render, screen, waitFor } from '@testing-library/react';
-import { CoreAdminContext } from 'ra-core';
+import { render, screen } from '@testing-library/react';
 
-import { ShowGuesser } from './ShowGuesser';
-import { ThemeProvider } from '../theme/ThemeProvider';
+import { ShowGuesser } from './ShowGuesser.stories';
 
 describe('<ShowGuesser />', () => {
     it('should log the guessed Show view based on the fetched record', async () => {
-        const logSpy = jest
-            .spyOn(console, 'log')
-            .mockImplementation(console.warn);
-        const dataProvider = {
-            getOne: () =>
-                Promise.resolve({
-                    data: {
-                        id: 123,
-                        authors: [
-                            { id: 1, name: 'john doe', dob: '1990-01-01' },
-                            { id: 2, name: 'jane doe', dob: '1992-01-01' },
-                        ],
-                        post_id: 6,
-                        score: 3,
-                        body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.",
-                        created_at: new Date('2012-08-02'),
-                        tags_ids: [1, 2],
-                    },
-                }),
-            getMany: () => Promise.resolve({ data: [] }),
-        };
-        render(
-            <ThemeProvider>
-                <CoreAdminContext dataProvider={dataProvider as any}>
-                    <ShowGuesser resource="comments" id={123} enableLog />
-                </CoreAdminContext>
-            </ThemeProvider>
-        );
-        await waitFor(() => {
-            screen.getByText('john doe');
-        });
+        const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
+        render(<ShowGuesser />);
+        await screen.findByText('john doe');
         expect(logSpy).toHaveBeenCalledWith(`Guessed Show:
 
-import { ArrayField, Datagrid, DateField, NumberField, ReferenceArrayField, ReferenceField, Show, SimpleShowLayout, TextField } from 'react-admin';
+import { ArrayField, BooleanField, Datagrid, DateField, EmailField, NumberField, ReferenceArrayField, ReferenceField, RichTextField, Show, SimpleShowLayout, TextField, UrlField } from 'react-admin';
 
-export const CommentShow = () => (
+export const BookShow = () => (
     <Show>
         <SimpleShowLayout>
             <TextField source="id" />
-            <ArrayField source="authors"><Datagrid><TextField source="id" />
-<TextField source="name" />
-<DateField source="dob" /></Datagrid></ArrayField>
+            <ArrayField source="authors">
+                <Datagrid>
+                    <TextField source="id" />
+                    <TextField source="name" />
+                    <DateField source="dob" />
+                </Datagrid>
+            </ArrayField>
             <ReferenceField source="post_id" reference="posts" />
             <NumberField source="score" />
             <TextField source="body" />
+            <RichTextField source="description" />
             <DateField source="created_at" />
             <ReferenceArrayField source="tags_ids" reference="tags" />
+            <UrlField source="url" />
+            <EmailField source="email" />
+            <BooleanField source="isAlreadyPublished" />
         </SimpleShowLayout>
     </Show>
 );`);
diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.stories.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.stories.tsx
new file mode 100644
index 00000000000..80d9bb66be0
--- /dev/null
+++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.stories.tsx
@@ -0,0 +1,48 @@
+import * as React from 'react';
+import { Admin } from 'react-admin';
+import { Resource, TestMemoryRouter } from 'ra-core';
+import fakeRestProvider from 'ra-data-fakerest';
+
+import { ShowGuesser as RAShowGuesser } from './ShowGuesser';
+
+export default { title: 'ra-ui-materialui/detail/ShowGuesser' };
+
+const data = {
+    books: [
+        {
+            id: 123,
+            authors: [
+                { id: 1, name: 'john doe', dob: '1990-01-01' },
+                { id: 2, name: 'jane doe', dob: '1992-01-01' },
+            ],
+            post_id: 6,
+            score: 3,
+            body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.",
+            description: `<p><strong>War and Peace</strong> is a novel by the Russian author <a href="https://en.wikipedia.org/wiki/Leo_Tolstoy">Leo Tolstoy</a>,
+published serially, then in its entirety in 1869.</p>
+<p>It is regarded as one of Tolstoy's finest literary achievements and remains a classic of world literature.</p>`,
+            created_at: new Date('2012-08-02'),
+            tags_ids: [1, 2],
+            url: 'https://www.myshop.com/tags/top-seller',
+            email: 'doe@production.com',
+            isAlreadyPublished: true,
+        },
+    ],
+    tags: [
+        { id: 1, name: 'top seller' },
+        { id: 2, name: 'new' },
+    ],
+    posts: [
+        { id: 6, title: 'War and Peace', body: 'A great novel by Leo Tolstoy' },
+    ],
+};
+
+const ShowGuesserWithProdLogs = () => <RAShowGuesser enableLog />;
+
+export const ShowGuesser = () => (
+    <TestMemoryRouter initialEntries={['/books/123/show']}>
+        <Admin dataProvider={fakeRestProvider(data)}>
+            <Resource name="books" show={ShowGuesserWithProdLogs} />
+        </Admin>
+    </TestMemoryRouter>
+);
diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx
index 2a9172699f1..165844e50e2 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx
@@ -190,7 +190,7 @@ const delayedDataProvider = fakeRestProvider(
     300
 );
 
-const ListGuesserWithProdLogs = props => <ListGuesser {...props} enableLog />;
+const ListGuesserWithProdLogs = () => <ListGuesser enableLog />;
 
 export const ManyResources = () => (
     <AdminContext

From 500fa3a6e0ff12a665e6bc64ec44848b091e4ce9 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Mon, 26 May 2025 15:59:19 +0200
Subject: [PATCH 14/20] use DataTable in ShowGuesser instead of Datagrid

---
 .../src/detail/ShowGuesser.tsx                |  8 ++++-
 .../src/detail/showFieldTypes.tsx             | 32 ++++++++++++-------
 .../ra-ui-materialui/src/list/ListGuesser.tsx |  1 +
 3 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx
index e21d81be0cc..d3ecc4fc15a 100644
--- a/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx
+++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx
@@ -72,10 +72,16 @@ const ShowViewGuesser = (
                 )
                 .sort();
 
+            const importsToLog = components.includes('DataTable')
+                ? components.filter(
+                      component => !component.startsWith('DataTable.')
+                  )
+                : components;
+
             console.log(
                 `Guessed Show:
 
-import { ${components.join(', ')} } from 'react-admin';
+import { ${importsToLog.join(', ')} } from 'react-admin';
 
 export const ${capitalize(singularize(resource))}Show = () => (
     <Show>
diff --git a/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx b/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
index 2ad60289748..ee2fd158ab4 100644
--- a/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
+++ b/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react';
 import { ReactNode } from 'react';
-import { Datagrid } from '../list/datagrid/Datagrid';
+import type { InferredElement, InferredTypeMap, InputProps } from 'ra-core';
 import {
     ArrayField,
     BooleanField,
@@ -17,8 +17,7 @@ import {
     ChipField,
 } from '../field';
 import { SimpleShowLayout, SimpleShowLayoutProps } from './SimpleShowLayout';
-import { InferredElement, InferredTypeMap, InputProps } from 'ra-core';
-import { SingleFieldList } from '../list';
+import { DataTable, SingleFieldList } from '../list';
 
 export const showFieldTypes: InferredTypeMap = {
     show: {
@@ -30,19 +29,30 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
         </SimpleShowLayout>`,
     },
     array: {
-        component: ({
-            children,
-            ...props
-        }: { children: ReactNode } & InputProps) => (
+        component: ({ children, ...props }: { children } & InputProps) => (
             <ArrayField {...props}>
-                <Datagrid>{children}</Datagrid>
+                <DataTable>
+                    {children && children.length > 0
+                        ? children.map(child => (
+                              <DataTable.Col source={child.props.source}>
+                                  {child}
+                              </DataTable.Col>
+                          ))
+                        : children}
+                </DataTable>
             </ArrayField>
         ),
         representation: (props: InputProps, children: InferredElement[]) =>
             `<ArrayField source="${props.source}">
-                <Datagrid>
-                    ${children.map(child => child.getRepresentation()).join('\n                    ')}
-                </Datagrid>
+                <DataTable>
+                    ${children
+                        .map(
+                            child => `<DataTable.Col source="${child.getProps().source}">
+                        ${child.getRepresentation()}
+                    </DataTable.Col>`
+                        )
+                        .join('\n                    ')}
+                </DataTable>
             </ArrayField>`,
     },
     boolean: {
diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.tsx
index bb69d4e718c..14a0b34b9cf 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.tsx
@@ -137,6 +137,7 @@ const ListViewGuesser = (
                           component => !component.startsWith('DataTable.')
                       )
                     : components;
+
                 console.log(
                     `Guessed List:
 

From b3bdd8595cb6b558431c192b4ad908cf8566bd63 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Mon, 26 May 2025 16:02:24 +0200
Subject: [PATCH 15/20] fix missing `key` prop

---
 packages/ra-ui-materialui/src/detail/showFieldTypes.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx b/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
index ee2fd158ab4..ce894912d73 100644
--- a/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
+++ b/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
@@ -33,8 +33,8 @@ ${children.map(child => `            ${child.getRepresentation()}`).join('\n')}
             <ArrayField {...props}>
                 <DataTable>
                     {children && children.length > 0
-                        ? children.map(child => (
-                              <DataTable.Col source={child.props.source}>
+                        ? children.map((child, index) => (
+                              <DataTable.Col key={index} {...child.props}>
                                   {child}
                               </DataTable.Col>
                           ))

From 66ce6d07c872d142c5d12e43833e9a9e3567991f Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Mon, 26 May 2025 16:02:37 +0200
Subject: [PATCH 16/20] adapt test

---
 .../src/detail/ShowGuesser.spec.tsx            | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
index b23bcc43179..ec2d2b6a7b0 100644
--- a/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
+++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
@@ -11,18 +11,24 @@ describe('<ShowGuesser />', () => {
         await screen.findByText('john doe');
         expect(logSpy).toHaveBeenCalledWith(`Guessed Show:
 
-import { ArrayField, BooleanField, Datagrid, DateField, EmailField, NumberField, ReferenceArrayField, ReferenceField, RichTextField, Show, SimpleShowLayout, TextField, UrlField } from 'react-admin';
+import { ArrayField, BooleanField, DataTable, DateField, EmailField, NumberField, ReferenceArrayField, ReferenceField, RichTextField, Show, SimpleShowLayout, TextField, UrlField } from 'react-admin';
 
 export const BookShow = () => (
     <Show>
         <SimpleShowLayout>
             <TextField source="id" />
             <ArrayField source="authors">
-                <Datagrid>
-                    <TextField source="id" />
-                    <TextField source="name" />
-                    <DateField source="dob" />
-                </Datagrid>
+                <DataTable>
+                    <DataTable.Col source="id">
+                        <TextField source="id" />
+                    </DataTable.Col>
+                    <DataTable.Col source="name">
+                        <TextField source="name" />
+                    </DataTable.Col>
+                    <DataTable.Col source="dob">
+                        <DateField source="dob" />
+                    </DataTable.Col>
+                </DataTable>
             </ArrayField>
             <ReferenceField source="post_id" reference="posts" />
             <NumberField source="score" />

From a912bca51cd5f497fcb9315d88c27566270d4c0c Mon Sep 17 00:00:00 2001
From: erwanMarmelab <131013150+erwanMarmelab@users.noreply.github.com>
Date: Tue, 27 May 2025 18:18:04 +0200
Subject: [PATCH 17/20] Use children instead of field

---
 docs/Tutorial.md | 24 ++++++++++++++++++------
 1 file changed, 18 insertions(+), 6 deletions(-)

diff --git a/docs/Tutorial.md b/docs/Tutorial.md
index 8cba51b5a80..5a7a89bc092 100644
--- a/docs/Tutorial.md
+++ b/docs/Tutorial.md
@@ -176,7 +176,9 @@ export const UserList = () => (
             <DataTable.Col source="id" />
             <DataTable.Col source="name" />
             <DataTable.Col source="username" />
-            <DataTable.Col source="email" field={EmailField} />
+            <DataTable.Col source="email">
+                <EmailField source="email" />
+            </DataTable.Col>
             <DataTable.Col source="address.street" />
             <DataTable.Col source="phone" />
             <DataTable.Col source="website" />
@@ -218,7 +220,9 @@ export const UserList = () => (
             <DataTable.Col source="id" />
             <DataTable.Col source="name" />
             <DataTable.Col source="username" />
-            <DataTable.Col source="email" field={EmailField} />
+            <DataTable.Col source="email">
+                <EmailField source="email" />
+	    </DataTable.Col>
             <DataTable.Col source="address.street" />
             <DataTable.Col source="phone" />
             <DataTable.Col source="website" />
@@ -297,7 +301,9 @@ export const UserList = () => {
                     <DataTable.Col source="id" />
                     <DataTable.Col source="name" />
                     <DataTable.Col source="username" />
-                    <DataTable.Col source="email" field={EmailField} />
+                    <DataTable.Col source="email">
+                        <EmailField source="email" />
+	            </DataTable.Col>
                     <DataTable.Col source="address.street" />
                     <DataTable.Col source="phone" />
                     <DataTable.Col source="website" />
@@ -334,7 +340,9 @@ That's a bit too much for a usable grid, so let's remove a couple of `<DataTable
     <DataTable.Col source="id" />
     <DataTable.Col source="name" />
 -   <DataTable.Col source="username" />
-    <DataTable.Col source="email" field={EmailField} />
+    <DataTable.Col source="email">
+      <EmailField source="email" />
+    </DataTable.Col>
 -   <DataTable.Col source="address.street" />
     <DataTable.Col source="phone" />
     <DataTable.Col source="website" />
@@ -361,10 +369,14 @@ For instance, instead of displaying the `website` field as plain text, you could
   <DataTable>
     <DataTable.Col source="id" />
     <DataTable.Col source="name" />
-    <DataTable.Col source="email" field={EmailField} />
+    <DataTable.Col source="email">
+      <EmailField source="email" />
+    </DataTable.Col>
     <DataTable.Col source="phone" />
 -   <DataTable.Col source="website" />
-+   <DataTable.Col source="website" field={UrlField} />
++   <DataTable.Col source="website">
++     <UrlField source="website" />
++   </DataTable.Col>
     <DataTable.Col source="company.name" />
   </DataTable>
 ```

From c0745dd2261313150546fcc7b767c80edd191c5d Mon Sep 17 00:00:00 2001
From: erwanMarmelab <131013150+erwanMarmelab@users.noreply.github.com>
Date: Tue, 3 Jun 2025 15:03:27 +0200
Subject: [PATCH 18/20] Apply suggestions from code review

Co-authored-by: Jean-Baptiste Kaiser <jb@marmelab.com>
---
 docs/Tutorial.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/docs/Tutorial.md b/docs/Tutorial.md
index 5a7a89bc092..f55b9d4fa01 100644
--- a/docs/Tutorial.md
+++ b/docs/Tutorial.md
@@ -222,7 +222,7 @@ export const UserList = () => (
             <DataTable.Col source="username" />
             <DataTable.Col source="email">
                 <EmailField source="email" />
-	    </DataTable.Col>
+            </DataTable.Col>
             <DataTable.Col source="address.street" />
             <DataTable.Col source="phone" />
             <DataTable.Col source="website" />
@@ -303,7 +303,7 @@ export const UserList = () => {
                     <DataTable.Col source="username" />
                     <DataTable.Col source="email">
                         <EmailField source="email" />
-	            </DataTable.Col>
+                    </DataTable.Col>
                     <DataTable.Col source="address.street" />
                     <DataTable.Col source="phone" />
                     <DataTable.Col source="website" />
@@ -328,7 +328,7 @@ The `<List>` component's child can be anything—even a custom component with it
 ## Selecting Columns
 
 Let's get back to `<DataTable>`.
-It reads the data fetched by `<List>`, then renders a table with one row for each record. `<DataTable>` uses its child components (here, a list of [Field component](./Fields.md)) to render the columns.
+It reads the data fetched by `<List>`, then renders a table with one row for each record. `<DataTable>` uses its child components (a list of `<DataTable.Col>` components) to render the columns.
 Each `<DataTable.Col>` component renders one field of the current record, specified by the `source` prop.
 
 `<ListGuesser>` created one column for every field in the API response.
@@ -356,7 +356,7 @@ In react-admin, most configuration is done through components. Instead of using
 
 ## Using Field Types
 
-So far, you've used simples [`<DataTable.Col>`](.//DataTable.md#datatablecol) and [`EmailField`](./EmailField.md) as [a `DataTable.Col` `field`](./DataTable.md#field).
+So far, you've used [`<DataTable.Col>`](./DataTable.md#datatablecol) directly and [`EmailField`](./EmailField.md) as [a `<DataTable.Col>` child](./DataTable.md#children-1).
 React-admin provides [many more Field components](./Fields.md) to handle different data types—numbers, dates, images, arrays, and more.
 
 For instance, instead of displaying the `website` field as plain text, you could make it a clickable link using [`<UrlField>`](./UrlField.md):

From 85ed6e92169d2a95e288638de7ebe98f2f738c66 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Tue, 3 Jun 2025 15:24:23 +0200
Subject: [PATCH 19/20] improve and fix the tutorial

---
 docs/Tutorial.md | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/docs/Tutorial.md b/docs/Tutorial.md
index f55b9d4fa01..351ce5832b3 100644
--- a/docs/Tutorial.md
+++ b/docs/Tutorial.md
@@ -358,6 +358,7 @@ In react-admin, most configuration is done through components. Instead of using
 
 So far, you've used [`<DataTable.Col>`](./DataTable.md#datatablecol) directly and [`EmailField`](./EmailField.md) as [a `<DataTable.Col>` child](./DataTable.md#children-1).
 React-admin provides [many more Field components](./Fields.md) to handle different data types—numbers, dates, images, arrays, and more.
+You can directly specify a field in your `DataTable.Col` using [the `field` prop](./DataTable.md#field), which is useful when no custom props are needed for that field.
 
 For instance, instead of displaying the `website` field as plain text, you could make it a clickable link using [`<UrlField>`](./UrlField.md):
 
@@ -374,9 +375,7 @@ For instance, instead of displaying the `website` field as plain text, you could
     </DataTable.Col>
     <DataTable.Col source="phone" />
 -   <DataTable.Col source="website" />
-+   <DataTable.Col source="website">
-+     <UrlField source="website" />
-+   </DataTable.Col>
++   <DataTable.Col source="website" field={UrlField} />
     <DataTable.Col source="company.name" />
   </DataTable>
 ```
@@ -421,10 +420,12 @@ You can use the `<MyUrlField>` component in `<UserList>` instead of react-admin'
   <DataTable>
     <DataTable.Col source="id" />
     <DataTable.Col source="name" />
-    <DataTable.Col source="email" source={EmailField} />
+    <DataTable.Col source="email">
+      <EmailField source="email" />
+    </DataTable.Col>
     <DataTable.Col source="phone" />
--   <DataTable.Col source="website" source={UrlField} />
-+   <DataTable.Col source="website" source={MyUrlField} />
+-   <DataTable.Col source="website" field={UrlField} />
++   <DataTable.Col source="website" field={MyUrlField} />
     <DataTable.Col source="company.name" />
   </DataTable>
 ```

From e4695e992bf9954f06f74ee0f4ee8a7587000d81 Mon Sep 17 00:00:00 2001
From: erwanMarmelab <erwan@marmelab.com>
Date: Tue, 3 Jun 2025 15:38:17 +0200
Subject: [PATCH 20/20] Match `Xxx.Xxx` instead of starting with `DataTable.`

---
 packages/ra-ui-materialui/src/detail/ShowGuesser.tsx | 9 ++-------
 packages/ra-ui-materialui/src/list/ListGuesser.tsx   | 9 ++-------
 2 files changed, 4 insertions(+), 14 deletions(-)

diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx
index d3ecc4fc15a..c2304283b56 100644
--- a/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx
+++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx
@@ -70,18 +70,13 @@ const ShowViewGuesser = (
                         )
                     )
                 )
+                .filter(component => !component.match(/[A-Za-z]+\.[A-Za-z]+/i))
                 .sort();
 
-            const importsToLog = components.includes('DataTable')
-                ? components.filter(
-                      component => !component.startsWith('DataTable.')
-                  )
-                : components;
-
             console.log(
                 `Guessed Show:
 
-import { ${importsToLog.join(', ')} } from 'react-admin';
+import { ${components.join(', ')} } from 'react-admin';
 
 export const ${capitalize(singularize(resource))}Show = () => (
     <Show>
diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.tsx
index 14a0b34b9cf..42f1f56e802 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.tsx
@@ -129,19 +129,14 @@ const ListViewGuesser = (
                         )
                     )
                 )
+                .filter(component => !component.match(/[A-Za-z]+\.[A-Za-z]+/i))
                 .sort();
 
             if (enableLog) {
-                const importsToLog = components.includes('DataTable')
-                    ? components.filter(
-                          component => !component.startsWith('DataTable.')
-                      )
-                    : components;
-
                 console.log(
                     `Guessed List:
 
-import { ${importsToLog.join(', ')} } from 'react-admin';
+import { ${components.join(', ')} } from 'react-admin';
 
 export const ${capitalize(singularize(resource))}List = () => (
     <List>