ra-editable-datagrid

The default react-admin user-experience consists of 3 pages: List, Edit, and Create. However, in some cases, users may prefer to do all search and edition tasks in one page. This package offers two ways to enhance the features of react-admin's <Datagrid> component, including an "edit-in-place" experience.

The first one is <EditableDatagrid>, a drop in replacement for <Datagrid> that allows users to edit, create, and delete records in place.

With <EditableDatagrid>, users can click on a row in the datagrid to replace the row with an edition form, and edit the corresponding record without leaving the list. They can also create new records by clicking on the Create button, which inserts an empty editable row as the first line of the list. Finally, they can delete a record by clicking on the Delete button on each row.

The second option is to use the <DatagridAG> component. <DatagridAG> is an alternative datagrid component with advanced features, based on ag-grid

Please note that <DatagridAG> is still in alpha stage though.

You can test both of them live in the Enterprise Edition Storybook and in the e-commerce demo.

Installation

npm install --save @react-admin/ra-editable-datagrid
# or
yarn add @react-admin/ra-editable-datagrid

Tip: ra-editable-datagrid is part of the React-Admin Enterprise Edition, and hosted in a private npm registry. You need to subscribe to one of the Enterprise Edition plans to access this package.

<EditableDatagrid>

<EditableDatagrid> renders like a regular <Datagrid>, adding some controls to create, edit, and delete records in place.

<EditableDatagrid> accepts the same props as <Datagrid>, plus 4 more props:

  • editForm (required): a component to display instead of a row when the users edit a record. It renders as many columns as the <EditableDatagrid> has children
  • createForm: a component to display as the first row when the user creates a record
  • mutationMode: Used for the update and delete mutations. It accepts the following values: pessimistic, optimistic, and undoable. See react-admin documentation about mutationMode.
  • noDelete: disable the inline Delete button

The <EditableDatagrid> component renders the editForm and createForm elements in a <table>, so these elements should render a <tr>. We advise you to use the <RowForm> component for editForm and createForm, which renders a <tr> by default. But you can also use your own component to render the row form (see below)

Tip: No need to include an <EditButton> as child, the <EditableDatagrid> component adds a column with edit/delete/save/cancel buttons itself.

Tip: To display a create button on top of the list, you should add the hasCreate prop to the <List> component, as in the example below.

Tip: To display a custom create button, pass a custom component as the empty prop. It can use the useEditableDatagridContext hook to access to openStandaloneCreateForm and closeStandaloneCreateForm callbacks.

Tip: To be able to add a new row when the list is empty, you need to bypass the default <List> empty page system by passing empty={false} as <List> prop.

import React from 'react';
import {
    List,
    ListProps,
    TextField,
    DateField,
    SelectField,
} from 'react-admin';
import { EditableDatagrid } from '@react-admin/ra-editable-datagrid';

import { ArtistForm } from './ArtistForm';

const ArtistList = () => (
    <List hasCreate empty={false}>
        <EditableDatagrid
            mutationMode="undoable"
            createForm={<ArtistForm />}
            editForm={<ArtistForm />}
        >
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
            <DateField source="dob" label="born" />
            <SelectField
                source="prof"
                label="Profession"
                choices={professionChoices}
            />
        </EditableDatagrid>
    </List>
);
import React from "react";
import { List, TextField, DateField, SelectField } from "react-admin";
import { EditableDatagrid } from "@react-admin/ra-editable-datagrid";

import { ArtistForm } from "./ArtistForm";

const ArtistList = () => (
    <List hasCreate empty={false}>
        <EditableDatagrid mutationMode="undoable" createForm={<ArtistForm />} editForm={<ArtistForm />}>
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
            <DateField source="dob" label="born" />
            <SelectField source="prof" label="Profession" choices={professionChoices} />
        </EditableDatagrid>
    </List>
);

Usage

Use <EditableDatagrid> as a child of a react-admin <List> or <ReferenceManyField>, in replacement of a <Datagrid>. In addition, pass a form component to be displayed when the user switches to edit or create mode.

import React from 'react';
import {
    List,
    TextField,
    TextInput,
    DateField,
    DateInput,
    SelectField,
    SelectInput,
    required,
} from 'react-admin';
import { EditableDatagrid, RowForm } from '@react-admin/ra-editable-datagrid';

const ArtistList = () => (
    <List hasCreate empty={false}>
        <EditableDatagrid
            mutationMode="undoable"
            createForm={<ArtistForm />}
            editForm={<ArtistForm />}
        >
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
            <DateField source="dob" label="born" />
            <SelectField
                source="prof"
                label="Profession"
                choices={professionChoices}
            />
        </EditableDatagrid>
    </List>
);

const ArtistForm = () => (
    <RowForm>
        <TextField source="id" />
        <TextInput source="firstname" validate={required()} />
        <TextInput source="name" validate={required()} />
        <DateInput source="dob" label="born" validate={required()} />
        <SelectInput
            source="prof"
            label="Profession"
            choices={professionChoices}
        />
    </RowForm>
);
import React from "react";
import { List, TextField, TextInput, DateField, DateInput, SelectField, SelectInput, required } from "react-admin";
import { EditableDatagrid, RowForm } from "@react-admin/ra-editable-datagrid";

const ArtistList = () => (
    <List hasCreate empty={false}>
        <EditableDatagrid mutationMode="undoable" createForm={<ArtistForm />} editForm={<ArtistForm />}>
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
            <DateField source="dob" label="born" />
            <SelectField source="prof" label="Profession" choices={professionChoices} />
        </EditableDatagrid>
    </List>
);

const ArtistForm = () => (
    <RowForm>
        <TextField source="id" />
        <TextInput source="firstname" validate={required()} />
        <TextInput source="name" validate={required()} />
        <DateInput source="dob" label="born" validate={required()} />
        <SelectInput source="prof" label="Profession" choices={professionChoices} />
    </RowForm>
);

Here is another example, this time inside a <ReferenceManyField>. The only difference with its usage in a <List> is that you have to initialize the foreign key in the create form using the defaultValues prop:

import React from 'react';
import {
    DateField,
    DateInput,
    Edit,
    NumberField,
    NumberInput,
    ReferenceManyField,
    required,
    SimpleForm,
    TextField,
    TextInput,
} from 'react-admin';
import { useFormContext } from 'react-hook-form';
import { EditableDatagrid, RowForm } from '@react-admin/ra-editable-datagrid';

const OrderEdit = () => (
    <Edit>
        <SimpleForm>
            <ReferenceManyField
                fullWidth
                label="Products"
                reference="products"
                target="order_id"
            >
                <EditableDatagrid
                    mutationMode="undoable"
                    createForm={<ProductForm />}
                    editForm={<ProductForm />}
                    rowClick="edit"
                >
                    <TextField source="id" />
                    <TextField source="name" />
                    <NumberField source="price" label="Default Price" />
                    <DateField source="available_since" />
                </EditableDatagrid>
            </ReferenceManyField>
            <DateInput source="purchase_date" />
        </SimpleForm>
    </Edit>
);

const ProductForm = () => {
    const { getValues } = useFormContext();
    return (
        <RowForm defaultValues={{ order_id: getValues('id') }}>
            <TextInput source="id" disabled />
            <TextInput source="name" validate={required()} />
            <NumberInput
                source="price"
                label="Default Price"
                validate={required()}
            />
            <DateInput source="available_since" validate={required()} />
        </RowForm>
    );
};
import React from "react";
import {
    DateField,
    DateInput,
    Edit,
    NumberField,
    NumberInput,
    ReferenceManyField,
    required,
    SimpleForm,
    TextField,
    TextInput,
} from "react-admin";
import { useFormContext } from "react-hook-form";
import { EditableDatagrid, RowForm } from "@react-admin/ra-editable-datagrid";

const OrderEdit = () => (
    <Edit>
        <SimpleForm>
            <ReferenceManyField fullWidth label="Products" reference="products" target="order_id">
                <EditableDatagrid
                    mutationMode="undoable"
                    createForm={<ProductForm />}
                    editForm={<ProductForm />}
                    rowClick="edit"
                >
                    <TextField source="id" />
                    <TextField source="name" />
                    <NumberField source="price" label="Default Price" />
                    <DateField source="available_since" />
                </EditableDatagrid>
            </ReferenceManyField>
            <DateInput source="purchase_date" />
        </SimpleForm>
    </Edit>
);

const ProductForm = () => {
    const { getValues } = useFormContext();
    return (
        <RowForm defaultValues={{ order_id: getValues("id") }}>
            <TextInput source="id" disabled />
            <TextInput source="name" validate={required()} />
            <NumberInput source="price" label="Default Price" validate={required()} />
            <DateInput source="available_since" validate={required()} />
        </RowForm>
    );
};

In these examples, the same form component is used in createForm and editForm, but you can pass different forms (e.g. if some fields can be set at creation but not changed afterwards).

Using Custom Actions

By default, the <EditableDatagrid> will show both edit and delete buttons when users hover a row. If you want to either customize the buttons behavior or provide more actions, you can leverage the actions prop, which accepts a React element. For instance, here's how to customize the delete button so that it asks users for a confirmation but still allows to undo the deletion:

import React from 'react';
import { List, TextField } from 'react-admin';
import {
    DeleteWithConfirmIconButton,
    EditableDatagrid,
    EditRowButton,
} from '@react-admin/ra-editable-datagrid';
import { ArtistForm } from './ArtistForm';

const CustomAction = () => (
    <>
        <EditRowButton />
        <DeleteWithConfirmIconButton mutationMode="undoable" />
    </>
);

const ArtistList = () => (
    <List hasCreate sort={{ field: 'id', order: 'DESC' }} empty={false}>
        <EditableDatagrid
            actions={<CustomAction />}
            // The mutation mode is still applied to updates
            mutationMode="undoable"
            editForm={<ArtistForm />}
        >
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
        </EditableDatagrid>
    </List>
);
import React from "react";
import { List, TextField } from "react-admin";
import { DeleteWithConfirmIconButton, EditableDatagrid, EditRowButton } from "@react-admin/ra-editable-datagrid";
import { ArtistForm } from "./ArtistForm";

const CustomAction = () => (
    <>
        <EditRowButton />
        <DeleteWithConfirmIconButton mutationMode="undoable" />
    </>
);

const ArtistList = () => (
    <List hasCreate sort={{ field: "id", order: "DESC" }} empty={false}>
        <EditableDatagrid
            actions={<CustomAction />}
            // The mutation mode is still applied to updates
            mutationMode="undoable"
            editForm={<ArtistForm />}
        >
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
        </EditableDatagrid>
    </List>
);

<RowForm>

<RowForm> renders a table row with one cell per child. That means that <RowForm> and <EditableDatagrid> should have the same number of children, and these children should concern the same source.

If you want to avoid the edition of a column, use a <Field> component instead of an <Input> component (like the <TextField> in the example above).

<RowForm> accepts the following props:

  • mutationOptions: An object that can contain onSuccess and onError functions to be executed after the row has been saved and after the row has failed to be saved respectively.
  • transform: A function to transform the row before it is saved.
  • submitOnEnter: Whether the form can be submitted by pressing the Enter key. Defaults to true.

Any additional props passed to <RowForm> are passed to the underlying react-admin <Form> component. That means that you can pass e.g. defaultValues, or validate props.

import { RowForm } from '@react-admin/ra-editable-datagrid';

const ArtistForm = () => (
    <RowForm defaultValues={{ firstname: 'John', name: 'Doe' }}>
        <TextField source="id" disabled />
        <TextInput source="name" validate={required()} />
    </RowForm>
);
import { RowForm } from "@react-admin/ra-editable-datagrid";

const ArtistForm = () => (
    <RowForm defaultValues={{ firstname: "John", name: "Doe" }}>
        <TextField source="id" disabled />
        <TextInput source="name" validate={required()} />
    </RowForm>
);

useEditableDatagridContext

For advanced use cases, you can use the useEditableDatagridContext hook to manage the visibility of the creation form. It returns the following callbacks:

  • openStandaloneCreateForm: A function to open the create form.
  • closeStandaloneCreateForm: A function to close the create form.

Recipes

Providing Custom Side Effects

Like other forms in react-admin, you can provide your own side effects in response to successful or failed actions by passing functions to the onSuccess or onError inside the mutationOptions prop:

import { TextInput, DateInput, SelectInput, useNotify } from 'react-admin';
import { RowForm, useRowContext } from '@react-admin/ra-editable-datagrid';

const ArtistEditForm = () => {
    const notify = useNotify();
    const { close } = useRowContext();

    const handleSuccess = response => {
        notify(
            `Artist ${response.name} ${response.firstName} has been updated`
        );
        close();
    };

    return (
        <RowForm mutationOptions={{ onSuccess: handleSuccess }}>
            <TextField source="id" />
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput
                source="prof"
                label="Profession"
                choices={professionChoices}
            />
        </RowForm>
    );
};

const ArtistCreateForm = () => {
    const notify = useNotify();
    const { close } = useRowContext();

    const handleSuccess = response => {
        notify(`Artist ${response.name} ${response.firstName} has been added`);
        close();
    };

    return (
        <RowForm mutationOptions={{ onSuccess: handleSuccess }}>
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput
                source="prof"
                label="Profession"
                choices={professionChoices}
            />
        </RowForm>
    );
};
import { TextInput, DateInput, SelectInput, useNotify } from "react-admin";
import { RowForm, useRowContext } from "@react-admin/ra-editable-datagrid";

const ArtistEditForm = () => {
    const notify = useNotify();
    const { close } = useRowContext();

    const handleSuccess = (response) => {
        notify(`Artist ${response.name} ${response.firstName} has been updated`);
        close();
    };

    return (
        <RowForm mutationOptions={{ onSuccess: handleSuccess }}>
            <TextField source="id" />
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput source="prof" label="Profession" choices={professionChoices} />
        </RowForm>
    );
};

const ArtistCreateForm = () => {
    const notify = useNotify();
    const { close } = useRowContext();

    const handleSuccess = (response) => {
        notify(`Artist ${response.name} ${response.firstName} has been added`);
        close();
    };

    return (
        <RowForm mutationOptions={{ onSuccess: handleSuccess }}>
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput source="prof" label="Profession" choices={professionChoices} />
        </RowForm>
    );
};

Note that we provide an additional side effects hook: useRowContext which allows you to close the form.

Tip: If you use useNotify inside an onSuccess side effect for an Edit form in addition to the <EditableDatagrid mutationMode="undoable"> prop, you will need to set the notification as undoable for the changes to take effects. Also, note that, on undoable forms, the onSuccess side effect will be called immediately, without any response argument.

const handleSuccess = () => {
    notify('Artist has been updated', { type: 'info', undoable: true });
    close();
};
const handleSuccess = () => {
    notify("Artist has been updated", { type: "info", undoable: true });
    close();
};

Besides, the <RowForm> also accept a function for its transform prop allowing you to alter the data before sending it to the dataProvider:

import { TextInput, DateInput, SelectInput } from 'react-admin';
import { RowForm } from '@react-admin/ra-editable-datagrid';

const ArtistCreateForm = () => {
    const handleTransform = data => {
        return {
            ...data,
            fullName: `${data.firstName} ${data.name}`,
        };
    };

    return (
        <RowForm transform={handleTransform}>
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput
                source="prof"
                label="Profession"
                choices={professionChoices}
            />
        </RowForm>
    );
};
import { TextInput, DateInput, SelectInput } from "react-admin";
import { RowForm } from "@react-admin/ra-editable-datagrid";

const ArtistCreateForm = () => {
    const handleTransform = (data) => {
        return {
            ...data,
            fullName: `${data.firstName} ${data.name}`,
        };
    };

    return (
        <RowForm transform={handleTransform}>
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput source="prof" label="Profession" choices={professionChoices} />
        </RowForm>
    );
};

Adding A meta Prop To All Mutations

Just like with <Datagrid>, if you'd like to add a meta prop to all the dataProvider calls, you will need to provide custom mutationOptions at all the places where mutations occur:

  • the createForm
  • the editForm
  • the <DeleteRowButton>

Here is a complete example:

import * as React from 'react';
import {
    TextInput,
    DateInput,
    SelectInput,
    TextField,
    DateField,
    SelectField,
    required,
    List,
} from 'react-admin';
import {
    EditableDatagrid,
    RowForm,
    RowFormProps,
    EditRowButton,
    DeleteRowButton,
} from '@react-admin/ra-editable-datagrid';

const ArtistForm = ({ meta }) => (
    <RowForm
        defaultValues={{ firstname: 'John', name: 'Doe' }}
        mutationOptions={{ meta }}
    >
        <TextField source="id" />
        <TextInput source="firstname" validate={required()} />
        <TextInput source="name" validate={required()} />
        <DateInput source="dob" label="Born" validate={required()} />
        <SelectInput
            source="prof"
            label="Profession"
            choices={professionChoices}
        />
    </RowForm>
);

const ArtistListWithMeta = () => {
    const meta = { foo: 'bar' };
    return (
        <List hasCreate sort={{ field: 'id', order: 'DESC' }} empty={false}>
            <EditableDatagrid
                createForm={<ArtistForm meta={meta} />}
                editForm={<ArtistForm meta={meta} />}
                rowClick="edit"
                actions={
                    <>
                        <EditRowButton />
                        <DeleteRowButton mutationOptions={{ meta }} />
                    </>
                }
            >
                <TextField source="id" />
                <TextField source="firstname" />
                <TextField source="name" />
                <DateField source="dob" label="Born" />
                <SelectField
                    source="prof"
                    label="Profession"
                    choices={professionChoices}
                />
            </EditableDatagrid>
        </List>
    );
};
import * as React from "react";
import { TextInput, DateInput, SelectInput, TextField, DateField, SelectField, required, List } from "react-admin";
import { EditableDatagrid, RowForm, EditRowButton, DeleteRowButton } from "@react-admin/ra-editable-datagrid";

const ArtistForm = ({ meta }) => (
    <RowForm defaultValues={{ firstname: "John", name: "Doe" }} mutationOptions={{ meta }}>
        <TextField source="id" />
        <TextInput source="firstname" validate={required()} />
        <TextInput source="name" validate={required()} />
        <DateInput source="dob" label="Born" validate={required()} />
        <SelectInput source="prof" label="Profession" choices={professionChoices} />
    </RowForm>
);

const ArtistListWithMeta = () => {
    const meta = { foo: "bar" };
    return (
        <List hasCreate sort={{ field: "id", order: "DESC" }} empty={false}>
            <EditableDatagrid
                createForm={<ArtistForm meta={meta} />}
                editForm={<ArtistForm meta={meta} />}
                rowClick="edit"
                actions={
                    <>
                        <EditRowButton />
                        <DeleteRowButton mutationOptions={{ meta }} />
                    </>
                }
            >
                <TextField source="id" />
                <TextField source="firstname" />
                <TextField source="name" />
                <DateField source="dob" label="Born" />
                <SelectField source="prof" label="Profession" choices={professionChoices} />
            </EditableDatagrid>
        </List>
    );
};

Configurable

You can let end users customize what fields are displayed in the <EditableDatagrid> by using the <EditableDatagridConfigurable> component instead, together with the <RowFormConfigurable> component.

import { List, TextField } from 'react-admin';
import {
-   EditableDatagrid,
+   EditableDatagridConfigurable,
-   RowForm,
+   RowFormConfigurable,
} from '@react-admin/ra-editable-datagrid';

const ArtistForm = ({ meta }) => (
-    <RowForm>
+    <RowFormConfigurable>
        <TextField source="id" />
        <TextInput source="firstname" validate={required()} />
        <TextInput source="name" validate={required()} />
        <DateInput source="dob" label="Born" validate={required()} />
        <SelectInput
            source="prof"
            label="Profession"
            choices={professionChoices}
        />
-   </RowForm>
+    </RowFormConfigurable>
);

const ArtistList = () => (
    <List hasCreate empty={false}>
-        <EditableDatagrid
+        <EditableDatagridConfigurable
            mutationMode="undoable"
            createForm={<ArtistForm />}
            editForm={<ArtistForm />}
        >
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
            <DateField source="dob" label="born" />
            <SelectField
                source="prof"
                label="Profession"
                choices={professionChoices}
            />
-        </EditableDatagrid>
+        </EditableDatagridConfigurable>
    </List>
);

When users enter the configuration mode and select the <EditableDatagrid>, they can show / hide datagrid columns. They can also use the <SelectColumnsButton>

By default, <EditableDatagridConfigurable> renders all child fields. But you can also omit some of them by passing an omit prop containing an array of field sources:

// by default, hide the id and author columns
// users can choose to show them in configuration mode
const PostList = () => (
    <List>
        <EditableDatagridConfigurable omit={['id', 'author']}>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="author" />
            <TextField source="year" />
        </EditableDatagridConfigurable>
    </List>
);
// by default, hide the id and author columns
// users can choose to show them in configuration mode
const PostList = () => (
    <List>
        <EditableDatagridConfigurable omit={["id", "author"]}>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="author" />
            <TextField source="year" />
        </EditableDatagridConfigurable>
    </List>
);

If you render more than one <EditableDatagridConfigurable> in the same page, you must pass a unique preferenceKey prop to each one:

const PostList = () => (
    <List>
        <EditableDatagridConfigurable preferenceKey="posts.datagrid">
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="author" />
            <TextField source="year" />
        </EditableDatagridConfigurable>
    </List>
);
const PostList = () => (
    <List>
        <EditableDatagridConfigurable preferenceKey="posts.datagrid">
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="author" />
            <TextField source="year" />
        </EditableDatagridConfigurable>
    </List>
);

The inspector uses the field source (or label when it's a string) to display the column name. If you use non-field children (e.g. action buttons), then it's your responsibility to wrap them in a component with a label prop, that will be used by the inspector:

const FieldWrapper = ({ children, label }) => children;
const PostList = () => (
    <List>
        <EditableDatagridConfigurable>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="author" />
            <TextField source="year" />
            <FieldWrapper label="Actions">
                <EditButton />
            </FieldWrapper>
        </EditableDatagridConfigurable>
    </List>
);
const FieldWrapper = ({ children, label }) => children;
const PostList = () => (
    <List>
        <EditableDatagridConfigurable>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="author" />
            <TextField source="year" />
            <FieldWrapper label="Actions">
                <EditButton />
            </FieldWrapper>
        </EditableDatagridConfigurable>
    </List>
);

<EditableDatagridConfigurable> accepts the same props as <EditableDatagrid>.

<DatagridAG>

<DatagridAG> is an alternative datagrid component with advanced features, based on ag-grid.

Note: This component is still in alpha stage. Major changes may still be introduced in the future.

Here is a (non-exhaustive) list of features that <DatagridAG> offers:

  • In place editing of cells or rows
  • Advanced filtering
  • Columns resizing and reordering
  • Automatic page size
  • Automatic column size
  • Themes
  • Row selection and bulk actions
  • Compatibility with React Admin fields

Additionally, <DatagridAG> is compatible with the Enterprise version of ag-grid, which offers even more features:

  • Row Grouping
  • Aggregation
  • Tree Data
  • Pivoting
  • More advanced filtering
  • Master Detail views
  • Range Selection
  • Excel Export
  • And more...

Usage

Use <DatagridAG> as a child of a react-admin <List>, <ReferenceManyField>, or any other component that creates a ListContext.

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [{ field: "title" }, { field: "published_at" }, { field: "body" }];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} />
        </List>
    );
};

DatagridAG PostList

Here are the important things to note:

  • You need to import the ag-grid stylesheets ag-grid.css and ag-theme-alpine.css.
  • To benefit from ag-grid's filtering and sorting features (as well as some Enterprise features like grouping), you need to load the entire list of records client-side. To do so, you must set <List perPage> to a high number (e.g. 10,000).
  • As the pagination is handled by ag-grid, you can disable react-admin's pagination with <List pagination={false}>.
  • The columns are defined using the columnDefs prop. See the dedicated doc section for more information.

<DatagridAG> doesn't currently support the server-side row model, so you have to load all data client-side. The client-side performance isn't affected by a large number of records, as ag-grid uses DOM virtualization. <DatagridAG> has been tested with 10,000 records without any performance issue.

Props

Prop Required Type Default Description
columnDefs Required Array n/a The columns definitions
bulkActionButtons Optional Element <BulkDelete Button> The component used to render the bulk action buttons
cellRenderer Optional String, Function or Element Allows to use a custom component to render the cell content
defaultColDef Optional Object The default column definition (applied to all columns)
mutationOptions Optional Object The mutation options
sx Optional Object The sx prop passed down to the wrapping <div> element
theme Optional String 'ag-theme-alpine' The name of the ag-grid theme

<DatagridAG> also accepts the same props as <AgGridReact> with the exception of rowData, since the data is fetched from the List context.

Defaults

Under the hood, <DatagridAG> is a wrapper around <AgGridReact>. However it sets some important default values:

  • pagination is set to true
  • paginationAutoPageSize is set to true
  • animateRows is set to true
  • rowSelection is set to 'multiple'
  • suppressRowClickSelection is set to true
  • readOnlyEdit is set to true
  • getRowId is set to use the record id field

It also includes a defaultColDef object with the following properties:

{
    resizable: true,
    filter: true,
    sortable: true,
    editable: true,
    headerCheckboxSelectionFilteredOnly: true,
    headerCheckboxSelectionCurrentPageOnly: true,
}

You may override any of these defaults by passing the corresponding props to <DatagridAG> (defaultColDef will be merged with the defaults).

columnDefs

The columnDefs prop is the most important prop of <DatagridAG>. It defines the columns of the grid, and their properties. It is an array of objects, each object representing a column.

Here is an example with a complete column definitions object:

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

const truncate = (str: string, n: number) => {
    return str.length > n ? str.slice(0, n - 1) + '...' : str;
};

export const PostList = () => {
    const columnDefs = [
        {
            field: 'id',
            editable: false,
            headerCheckboxSelection: true,
            checkboxSelection: true,
            minWidth: 48,
            maxWidth: 48,
        },
        { field: 'title' },
        {
            field: 'published_at',
            headerName: 'Publication Date',
        },
        {
            field: 'body',
            cellRenderer: ({ value }) => truncate(value, 20),
        },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

const truncate = (str, n) => {
    return str.length > n ? str.slice(0, n - 1) + "..." : str;
};

export const PostList = () => {
    const columnDefs = [
        {
            field: "id",
            editable: false,
            headerCheckboxSelection: true,
            checkboxSelection: true,
            minWidth: 48,
            maxWidth: 48,
        },
        { field: "title" },
        {
            field: "published_at",
            headerName: "Publication Date",
        },
        {
            field: "body",
            cellRenderer: ({ value }) => truncate(value, 20),
        },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} />
        </List>
    );
};

DatagridAG custom columnDefs

Have a look at the ag-grid documentation for the exhaustive list of column properties.

defaultColDef

The defaultColDef prop allows you to define default properties for all columns. It is an object with the same properties as columnDefs objects.

In the example below, we enable flex mode on the columns, and set each column to take 1/3 of the available space:

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    const defaultColDef = {
        flex: 1,
    };
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} defaultColDef={defaultColDef} />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [{ field: "title" }, { field: "published_at" }, { field: "body" }];
    const defaultColDef = {
        flex: 1,
    };
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} defaultColDef={defaultColDef} />
        </List>
    );
};

DatagridAG defaultColDef

cellRenderer

In a column definition, you can use the cellRenderer field to specify a custom cell renderer. In addition to ag-grid's cell rendering abilities, <DatagridAG> supports react-admin fields in cellRenderer. This is particularly useful to render a <ReferenceField> for instance.

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { EmailField, List, ReferenceField, TextField } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const CommentList = () => {
    const columnDefs = [
        {
            field: 'id',
            editable: false,
        },
        { field: 'author.name' },
        {
            field: 'author.email',
            cellRenderer: <EmailField source="author.email" />,
        },
        {
            field: 'post_id',
            headerName: 'Post',
            cellRenderer: (
                <ReferenceField source="post_id" reference="posts">
                    <TextField source="title" />
                </ReferenceField>
            ),
        },
        { field: 'created_at' },
        { field: 'body' },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { EmailField, List, ReferenceField, TextField } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const CommentList = () => {
    const columnDefs = [
        {
            field: "id",
            editable: false,
        },
        { field: "author.name" },
        {
            field: "author.email",
            cellRenderer: <EmailField source="author.email" />,
        },
        {
            field: "post_id",
            headerName: "Post",
            cellRenderer: (
                <ReferenceField source="post_id" reference="posts">
                    <TextField source="title" />
                </ReferenceField>
            ),
        },
        { field: "created_at" },
        { field: "body" },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} />
        </List>
    );
};

DatagridAG RA Fields

Note: You still need to pass the source prop to the field.

bulkActionButtons

You can use the bulkActionButtons prop to customize the bulk action buttons, displayed when at least one row is selected.

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List, BulkExportButton, BulkDeleteButton } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

const PostBulkActionButtons = () => (
    <>
        <BulkExportButton />
        <BulkDeleteButton />
    </>
);

export const PostList = () => {
    const columnDefs = [
        {
            headerCheckboxSelection: true,
            checkboxSelection: true,
            editable: false,
            minWidth: 48,
            maxWidth: 48,
        },
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG 
                columnDefs={columnDefs}
                bulkActionButtons={<PostBulkActionButtons />}
            />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List, BulkExportButton, BulkDeleteButton } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

const PostBulkActionButtons = () => (
    <>
        <BulkExportButton />
        <BulkDeleteButton />
    </>
);

export const PostList = () => {
    const columnDefs = [
        {
            headerCheckboxSelection: true,
            checkboxSelection: true,
            editable: false,
            minWidth: 48,
            maxWidth: 48,
        },
        { field: "title" },
        { field: "published_at" },
        { field: "body" },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} bulkActionButtons={<PostBulkActionButtons />} />
        </List>
    );
};

mutationOptions

You can use the mutationOptions prop to provide options to the dataProvider.update() call triggered when a cell or a row is edited.

In particular, this allows to choose the mutationMode, and/or to pass a meta object to the dataProvider.

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG
                columnDefs={columnDefs}
                mutationOptions={{
                    meta: { foo: 'bar' },
                    mutationMode: 'optimistic',
                }}
            />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [{ field: "title" }, { field: "published_at" }, { field: "body" }];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG
                columnDefs={columnDefs}
                mutationOptions={{
                    meta: { foo: "bar" },
                    mutationMode: "optimistic",
                }}
            />
        </List>
    );
};

theme

You can use a different theme for the grid by passing a theme prop. You can for instance use one of the themes provided by ag-grid, like ag-theme-balham or ag-theme-alpine-dark:

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} theme="ag-theme-alpine-dark" />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [{ field: "title" }, { field: "published_at" }, { field: "body" }];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} theme="ag-theme-alpine-dark" />
        </List>
    );
};

DatagridAG Dark

Tip: Remember to import the corresponding stylesheet (e.g. ag-theme-balham[.min].css for ag-theme-balham).

sx

You can also use the sx prop to customize the grid's style:

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG
                columnDefs={columnDefs}
                sx={{ '& .ag-header-cell-comp-wrapper': { color: 'red' } }}
            />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [{ field: "title" }, { field: "published_at" }, { field: "body" }];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} sx={{ "& .ag-header-cell-comp-wrapper": { color: "red" } }} />
        </List>
    );
};

DatagridAG sx

It can also be helpful to change the default grid's height (calc(100vh - 96px - ${theme.spacing(1)})):

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        /* ... */
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG
                columnDefs={columnDefs}
                sx={{ height: 'calc(100vh - 250px)' }}
            />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [
        /* ... */
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} sx={{ height: "calc(100vh - 250px)" }} />
        </List>
    );
};

DatagridAG sx height

Accessing The Grid And Column APIs

You can access the grid's api and columnApi by passing a ref to <DatagridAG>.

In this example, we use the columnApi to automatically resize all columns to fit their content on first render:

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { AgGridReact } from 'ag-grid-react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    const gridRef = React.useRef<AgGridReact>(null);
    const onFirstDataRendered = React.useCallback(() => {
        gridRef.current.columnApi.autoSizeAllColumns();
    }, []);
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG
                columnDefs={columnDefs}
                ref={gridRef}
                onFirstDataRendered={onFirstDataRendered}
            />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [{ field: "title" }, { field: "published_at" }, { field: "body" }];
    const gridRef = React.useRef(null);
    const onFirstDataRendered = React.useCallback(() => {
        gridRef.current.columnApi.autoSizeAllColumns();
    }, []);
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} ref={gridRef} onFirstDataRendered={onFirstDataRendered} />
        </List>
    );
};

Check out the Grid API and Column API documentations to learn more.

Changing The Default Column Width

By default, ag-grid will render each column with a fixed size.

You can choose to enable flex mode by setting the flex prop either on the columnDefs or on the defaultColDef:

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        { field: 'title' },
        { field: 'published_at', flex: 1 },
        { field: 'body' },
    ];
    const defaultColDef = {
        flex: 2,
    };
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} defaultColDef={defaultColDef} />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [{ field: "title" }, { field: "published_at", flex: 1 }, { field: "body" }];
    const defaultColDef = {
        flex: 2,
    };
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} defaultColDef={defaultColDef} />
        </List>
    );
};

DatagridAG flex

Alternatively, you can use the grid's columnApi to call autoSizeAllColumns to automatically resize all columns to fit their content:

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { AgGridReact } from 'ag-grid-react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    const gridRef = React.useRef<AgGridReact>(null);
    const onFirstDataRendered = React.useCallback(() => {
        gridRef.current.columnApi.autoSizeAllColumns();
    }, []);
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG
                columnDefs={columnDefs}
                ref={gridRef}
                onFirstDataRendered={onFirstDataRendered}
            />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [{ field: "title" }, { field: "published_at" }, { field: "body" }];
    const gridRef = React.useRef(null);
    const onFirstDataRendered = React.useCallback(() => {
        gridRef.current.columnApi.autoSizeAllColumns();
    }, []);
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} ref={gridRef} onFirstDataRendered={onFirstDataRendered} />
        </List>
    );
};

DatagridAG auto size

Check out the Column Sizing documentation for more information and more alternatives.

Selecting Rows And Enabling Bulk Actions

Just like <Datagrid>, <DatagridAG> supports row selection and bulk actions.

Add a column with the following definition to enable row selection:

{
    headerCheckboxSelection: true,
    checkboxSelection: true,
    editable: false,
    minWidth: 48,
    maxWidth: 48,
},

Below is an example with the PostList component:

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        {
            headerCheckboxSelection: true,
            checkboxSelection: true,
            editable: false,
            minWidth: 48,
            maxWidth: 48,
        },
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [
        {
            headerCheckboxSelection: true,
            checkboxSelection: true,
            editable: false,
            minWidth: 48,
            maxWidth: 48,
        },
        { field: "title" },
        { field: "published_at" },
        { field: "body" },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} />
        </List>
    );
};

DatagridAG selected rows

Just like with <Datagrid>, you can customize the bulk actions by passing a bulkActionButtons prop to <DatagridAG>.

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List, BulkExportButton, BulkDeleteButton } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

const PostBulkActionButtons = () => (
    <>
        <BulkExportButton />
        <BulkDeleteButton />
    </>
);

export const PostList = () => {
    const columnDefs = [
        {
            headerCheckboxSelection: true,
            checkboxSelection: true,
            editable: false,
            minWidth: 48,
            maxWidth: 48,
        },
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG 
                columnDefs={columnDefs}
                bulkActionButtons={<PostBulkActionButtons />}
            />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List, BulkExportButton, BulkDeleteButton } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

const PostBulkActionButtons = () => (
    <>
        <BulkExportButton />
        <BulkDeleteButton />
    </>
);

export const PostList = () => {
    const columnDefs = [
        {
            headerCheckboxSelection: true,
            checkboxSelection: true,
            editable: false,
            minWidth: 48,
            maxWidth: 48,
        },
        { field: "title" },
        { field: "published_at" },
        { field: "body" },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} bulkActionButtons={<PostBulkActionButtons />} />
        </List>
    );
};

Enabling Full Row Edition

By default, editing is enabled on cells, which means you can edit a cell by double-clicking on it, and it will trigger a call to the dataProvider's update function.

DatagridAG edit cell

However, if you'd like to update the full row at once instead, you can enable full row editing by passing editType="fullRow" to <DatagridAG>:

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        /* ... */
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} editType="fullRow" />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [
        /* ... */
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} editType="fullRow" />
        </List>
    );
};

DatagridAG edit row

Disabling Cell Edition

Set editable: false in the definition of a column to disable the ability to edit its cells.

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        { field: 'title' },
        { field: 'published_at', editable: false },
        { field: 'body' },
    ];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [{ field: "title" }, { field: "published_at", editable: false }, { field: "body" }];
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} />
        </List>
    );
};

Alternatively, you can disable the ability to edit all cells by passing editable: false to the defaultColDef:

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

export const PostList = () => {
    const columnDefs = [
        { field: 'title' },
        { field: 'published_at' },
        { field: 'body' },
    ];
    const defaultColDef = {
        editable: false,
    };
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG
                columnDefs={columnDefs}
                defaultColDef={defaultColDef}
            />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

export const PostList = () => {
    const columnDefs = [{ field: "title" }, { field: "published_at" }, { field: "body" }];
    const defaultColDef = {
        editable: false,
    };
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG columnDefs={columnDefs} defaultColDef={defaultColDef} />
        </List>
    );
};

Using AG Grid Enterprise

<DatagridAG> is also compatible with the Enterprise version of ag-grid.

You can follow the instructions in the Getting Started with AG Grid Enterprise section of the Getting Started documentation to enable the Enterprise features.

Below is a short example of what you can achieve.

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-enterprise';
import React from 'react';
import { List } from 'react-admin';
import { DatagridAG } from '@react-admin/ra-editable-datagrid';

const OlympicWinnersList = () => {
    const columnDefs = [
        {
            headerCheckboxSelection: true,
            checkboxSelection: true,
            editable: false,
            minWidth: 48,
            maxWidth: 48,
        },
        { field: 'athlete' },
        { field: 'age' },
        { field: 'country' },
        { field: 'year' },
        { field: 'date' },
        { field: 'sport' },
        { field: 'gold' },
        { field: 'silver' },
        { field: 'bronze' },
        { field: 'total' },
    ];
    const gridRef = React.useRef<AgGridReact>(null);
    const onFirstDataRendered = React.useCallback(() => {
        gridRef.current.columnApi.autoSizeAllColumns();
    }, []);
    const defaultColDef = {
        enableRowGroup: true,
    };
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG
                columnDefs={columnDefs}
                defaultColDef={defaultColDef}
                ref={gridRef}
                onFirstDataRendered={onFirstDataRendered}
                rowGroupPanelShow="always"
                groupSelectsChildren
            />
        </List>
    );
};
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import "ag-grid-enterprise";
import React from "react";
import { List } from "react-admin";
import { DatagridAG } from "@react-admin/ra-editable-datagrid";

const OlympicWinnersList = () => {
    const columnDefs = [
        {
            headerCheckboxSelection: true,
            checkboxSelection: true,
            editable: false,
            minWidth: 48,
            maxWidth: 48,
        },
        { field: "athlete" },
        { field: "age" },
        { field: "country" },
        { field: "year" },
        { field: "date" },
        { field: "sport" },
        { field: "gold" },
        { field: "silver" },
        { field: "bronze" },
        { field: "total" },
    ];
    const gridRef = React.useRef(null);
    const onFirstDataRendered = React.useCallback(() => {
        gridRef.current.columnApi.autoSizeAllColumns();
    }, []);
    const defaultColDef = {
        enableRowGroup: true,
    };
    return (
        <List perPage={10000} pagination={false}>
            <DatagridAG
                columnDefs={columnDefs}
                defaultColDef={defaultColDef}
                ref={gridRef}
                onFirstDataRendered={onFirstDataRendered}
                rowGroupPanelShow="always"
                groupSelectsChildren
            />
        </List>
    );
};

CHANGELOG

v4.5.1

2023-10-30

  • Fix <DatagridAG> default defaultColDef are mutated when a defaultColDef prop is provided

v4.5.0

2023-10-20

  • Introduce <DatagridAG>

v4.4.0

2023-09-29

  • Add <EditableDatagridConfigurable> and <RowFormConfigurable> components

v4.3.2

2023-09-19

  • Add support for in <EditableDatagrid rowSx> prop

v4.3.1

2023-07-11

  • Fix <EditableDatagrid> does not forward the header and size props to <Datagrid>

v4.3.0

2023-05-24

  • Fix <RowForm> does not pass mutationOptions with only meta
  • Fix <DeleteRowButton> does not support mutationOptions
  • Upgraded to react-admin 4.10.6

v4.2.2

2023-04-26

  • Fix <SaveRowButton> is not disabled while saving.

v4.2.1

2023-04-07

  • Fix <RowForm> doesn't call the provided transform function when submitting with the Enter key

v4.2.0

2023-03-06

  • Fix <EditableDatagrid> doesn't render the table headers when creating the first row
  • Upgraded to react-admin 4.8.2

v4.1.4

2023-02-09

  • Fix <EditableDatagrid> doesn't render a Create button when used inside a ListContextProvider and having a createForm

v4.1.3

2023-01-31

  • Fix <EditableDatagrid> might enter a infinite render loop when empty and a custom empty component was provided.

v4.1.2

2023-01-05

  • Fix DeleteWithConfirmIconButton props: added mutationOptions, translateOptions and removed unused invalid, pristine, saving
  • Fix DeleteWithConfirmIconButton ignores onClick
  • Fix propTypes definition in EditableDatagrid
  • Fix custom empty component is not rendered when using EditableDatagrid in list view
  • Fix useEditableDatagridContext is not exported
  • Fix EditableDatagridProps type

v4.1.1

2022-11-07

  • (feat) Add ability to use useEditableDatagridContext callbacks even when in a List view

v4.1.0

2022-09-26

  • (feat) Add EditableDatagridContext with useEditableDatagridContext hook, and allow customization of the create button with the empty prop in case there is no data inside an <EditableDatagrid>

v4.0.7

2022-09-27

  • (fix) Fix ability to pass sx prop to EditableDatagrid

v4.0.6

2022-09-19

  • Fix rowClick="show" disables row edition

v4.0.5

2022-09-05

  • (fix) Fix transform is not called when no mutationOptions are provided
  • Storybook: add stories to demonstrate transform

v4.0.4

2022-08-23

  • (fix) Remove mutationMode prop from <RowForm> (mutationMode is supported on <EditableDatagrid> only)
  • Doc fixes
    • handleSuccess example was using response.data instead of data
    • undoable notification example was wrong
    • link to OSS mutationMode was broken
    • one <EditableDatagrid> example still mentioned undoable prop instead of mutationMode
  • Storybook: add stories to demonstrate mutationMode and side effects

v4.0.3

2022-08-04

  • Fix: Passing custom actions to an <EditableDatagrid> used inside a <ReferenceManyField> no longer hide the Create button.

v4.0.2

2022-06-29

  • Fix: Replace classnames with clsx

v4.0.1

2022-06-08

  • (fix) Update peer dependencies ranges (support React 18)

v4.0.0

2022-06-07

  • Upgrade to react-admin v4

Breaking changes

  • The useCreateRowContext and useEditRowContext hooks have been replaced by a single useRowContext.
import {
    RowForm,
-    useCreateRowContext,
-    useEditRowContext,
+    useRowContext,
} from '@react-admin/ra-editable-datagrid';

const ArtistEditForm = () => {
    const notify = useNotify();
-    const { close } = useEditRowContext();
+    const { close } = useRowContext();

    const handleSuccess = response => {
        notify(
            `Artist ${response.name} ${response.firstName} has been updated`
        );
        close();
    };

    return (
        <RowForm mutationOptions={{ onSuccess: handleSuccess }}>
            // ...
        </RowForm>
    );
};

const ArtistCreateForm = () => {
    const notify = useNotify();
-    const { close } = useCreateRowContext();
+    const { close } = useRowContext();

    const handleSuccess = response => {
        notify(
            `Artist ${response.name} ${response.firstName} has been added`
        );
        close();
    };

    return (
        <RowForm mutationOptions={{ onSuccess: handleSuccess }}>
            // ...
        </RowForm>
    );
};
  • The undoable prop has been removed, use mutationMode instead. It accepts the following values: pessimistic, optimistic, and undoable. See react-admin documentation about mutationMode.
<EditableDatagrid
-    undoable
+    mutationMode="undoable"
// ...
```

v2.1.2

2021-11-19

  • (fix) Fix sanitize title prop out of EditDialog component

v2.1.1

2021-09-20

  • (fix) fix error happening after clicking the create button in an empty Datagrid with expand

v2.1.0

2021-09-06

  • (feat) allows to disable submit on enter with the submitOnEnter prop on RowForm

v2.0.1

2021-08-06

  • (fix) Correctly handle prop actions when set to false

v2.0.0

2021-08-04

  • (fix) Correctly handle prop actions when set to false

  • (fix) Breaking change: Renamed CreateResourceButton to CreateButton

  • (feat) Add support for the mutationMode prop introduced in react-admin@3.12.0.

    The undoable prop is deprecated, use mutationMode instead. It accepts the following values: pessimistic, optimistic, and undoable. See react-admin documentation about mutationMode.

  • (feat) Add support for custom actions on <EditableDatagrid> through the actions prop.

For instance, you could provide your own DeleteButton with confirmation and undoable notification:

import React from 'react';
import { List, TextField } from 'react-admin';
import {
    DeleteWithConfirmIconButton,
    EditableDatagrid,
    EditRowButton,
} from '@react-admin/ra-editable-datagrid';
import { ArtistForm } from './ArtistForm';

const CustomAction = () => (
    <>
        <EditRowButton />
        <DeleteWithConfirmIconButton mutationMode="undoable" />
    </>
);

const ArtistList = (props: ListProps) => (
    <List
        {...props}
        hasCreate
        sort={{ field: 'id', order: 'DESC' }}
        empty={false}
    >
        <EditableDatagrid
            actions={<CustomAction />}
            // The mutation mode is still applied to updates
            mutationMode="undoable"
            editForm={<ArtistForm />}
        >
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
        </EditableDatagrid>
    </List>
);
import React from "react";
import { List, TextField } from "react-admin";
import { DeleteWithConfirmIconButton, EditableDatagrid, EditRowButton } from "@react-admin/ra-editable-datagrid";
import { ArtistForm } from "./ArtistForm";

const CustomAction = () => (
    <>
        <EditRowButton />
        <DeleteWithConfirmIconButton mutationMode="undoable" />
    </>
);

const ArtistList = (props) => (
    <List {...props} hasCreate sort={{ field: "id", order: "DESC" }} empty={false}>
        <EditableDatagrid
            actions={<CustomAction />}
            // The mutation mode is still applied to updates
            mutationMode="undoable"
            editForm={<ArtistForm />}
        >
            <TextField source="id" />
            <TextField source="firstname" />
            <TextField source="name" />
        </EditableDatagrid>
    </List>
);

v1.3.5

2021-07-28

  • (fix) Ensure submitting on enter does not discard validation errors

v1.3.4

2021-07-27

  • (fix) Update Delete Icon buttons to use ra-core hooks

v1.3.3

2021-07-19

  • (fix) Fix creating a record fails when the Datagrid is empty

v1.3.2

2021-07-16

  • (fix) Fix Edit button does not respond to click

v1.3.1

2021-06-29

  • (fix) Update peer dependencies ranges (support react 17)

v1.3.0

2021-06-14

  • (feat) Add support for side effects on <RowForm>.

You can now provide your own side effects in response to successful/failed update/creation in an <EditableDatagrid> by passing functions to the onSuccess or onFailure props:

import {
    DateInput,
    SelectInput,
    TextField,
    TextInput,
    useNotify,
} from 'react-admin';
import {
    RowForm,
    RowFormProps,
    useEditRowContext,
    useCreateRowContext,
} from '@react-admin/ra-editable-datagrid';

const ArtistEditForm = (props: RowFormProps) => {
    const notify = useNotify();
    const { close } = useEditRowContext();

    const handleSuccess = response => {
        notify(
            `Artist ${response.data.name} ${response.data.firstName} has been updated`
        );
        close();
    };

    return (
        <RowForm onSuccess={handleSuccess} {...props}>
            <TextField source="id" />
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput
                source="prof"
                label="Profession"
                choices={professionChoices}
            />
        </RowForm>
    );
};

const ArtistCreateForm = props => {
    const notify = useNotify();
    const { close } = useCreateRowContext();

    const handleSuccess = response => {
        notify(
            `Artist ${response.data.name} ${response.data.firstName} has been added`
        );
        close();
    };

    return (
        <RowForm onSuccess={handleSuccess} {...props}>
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput
                source="prof"
                label="Profession"
                choices={professionChoices}
            />
        </RowForm>
    );
};
import { DateInput, SelectInput, TextField, TextInput, useNotify } from "react-admin";
import { RowForm, useEditRowContext, useCreateRowContext } from "@react-admin/ra-editable-datagrid";

const ArtistEditForm = (props) => {
    const notify = useNotify();
    const { close } = useEditRowContext();

    const handleSuccess = (response) => {
        notify(`Artist ${response.data.name} ${response.data.firstName} has been updated`);
        close();
    };

    return (
        <RowForm onSuccess={handleSuccess} {...props}>
            <TextField source="id" />
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput source="prof" label="Profession" choices={professionChoices} />
        </RowForm>
    );
};

const ArtistCreateForm = (props) => {
    const notify = useNotify();
    const { close } = useCreateRowContext();

    const handleSuccess = (response) => {
        notify(`Artist ${response.data.name} ${response.data.firstName} has been added`);
        close();
    };

    return (
        <RowForm onSuccess={handleSuccess} {...props}>
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput source="prof" label="Profession" choices={professionChoices} />
        </RowForm>
    );
};

Note that we provide two additional side effects hooks: useEditRowContext and useCloseCreateRow, which allow you to close the form.

Besides, the <RowForm> also accepts a function for its transform prop, allowing you to alter the data before sending it to the dataProvider:

const ArtistCreateForm = props => {
    const handleTransform = data => {
        return {
            ...data,
            fullName: `${data.firstName} ${data.name}`,
        };
    };

    return (
        <RowForm transform={handleTransform} {...props}>
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput
                source="prof"
                label="Profession"
                choices={professionChoices}
            />
        </RowForm>
    );
};
const ArtistCreateForm = (props) => {
    const handleTransform = (data) => {
        return {
            ...data,
            fullName: `${data.firstName} ${data.name}`,
        };
    };

    return (
        <RowForm transform={handleTransform} {...props}>
            <TextInput source="firstname" validate={required()} />
            <TextInput source="name" validate={required()} />
            <DateInput source="dob" label="born" validate={required()} />
            <SelectInput source="prof" label="Profession" choices={professionChoices} />
        </RowForm>
    );
};

v1.2.0

2021-05-17

  • (chore) Upgrade to react-admin 3.15
  • (feat) Ensure all components with custom styles are overridable through MUI theme by providing their key (RaEditableDatagrid, RaEditableDatagridRow and RaRowForm).

v1.1.2

2021-05-11

  • Fix <EditableDatagrid> fails when wrapped

v1.1.1

2020-11-03

  • Add the noDelete prop to <EditableDatagrid> to disable the inline Delete button

v1.1.0

2020-10-05

  • Upgrade to react-admin 3.9

v1.0.4

2020-09-30

  • Update Readme

v1.0.3

2020-09-17

  • (fix) Fix crash on row deletion and impossible row creation

v1.0.2

2020-09-16

  • (deps) Upgrade dependencies

v1.0.1

2020-09-07

  • (feat) Add support for expand

v1.0.0

2020-08-05

  • First release