React-admin 2.6: Datagrid Improvements, UI Polish, and Better Styling

François Zaninotto
François ZaninottoJanuary 09, 2019
#admin-on-rest#react-admin#react#oss

Thanks to the hard work of more than a dozen developers, 2019 starts with a new react-admin release, version 2.6. If the words "react-admin" don't mean anything to you, they relate to a frontend framework for building admin GUIs on top of REST/GraphQL APIs, that we've been working on for more than 2 years, and that is stable, used in production, and open-source.

And since we didn't blog about the new features of the previous minor version (react-admin 2.5), this post will list the enhancements from both versions. Indeed, if you're still using react-admin 2.4, upgrading to react-admin 2.6 will change a lot of little things for admin developers and end users - without any breaking changes! Here are the highlights:

Let's dive in!

Datagrid Expansion Panel

The <Datagrid> component is a central part of many admin UIs. With react-admin 2.5 and 2.6, we've made it even more flexible, so that it can be used in more use cases.

As a starter, you can now attach an expansion panel to every datagrid row. So if the tabular presentation isn't enough to show important information, you can still make it available one click away! For instance, the following code shows the body of a post in an expandable panel:

const PostPanel = ({ id, record, resource }) => (
    <div dangerouslySetInnerHTML={{ __html: record.body }} />
);

const PostList = props => (
    <List {...props}>
        <Datagrid expand={<PostPanel />}>
            <TextField source="id" />
            <TextField source="title" />
            <DateField source="published_at" />
            <BooleanField source="commentable" />
            <NumberField source="views" />
            <EditButton />
            <ShowButton />
        </Datagrid>
    </List>
)

expandable panel

The expand prop expects a react element. When the user chooses to expand the row, the Datagrid clones the element and passes the current record, id, and resource.

Tip: Since the expand element receives the same props as a detail view, you can actually use an <Edit> view as expand element, albeit with a twist:

const UserEdit = props => (
    <Edit 
        {...props}
        /* disable the app title change when shown */
        title=" "
    >
        <SimpleForm
            /* The form must have a name dependent on the record, because by default all forms have the same name */
            form={`post_edit_${props.id}`}
        >
            <TextInput source="name" />
        </SimpleForm>
    </Edit>
);

const UserList = props => (
    <List {...props}>
        <Datagrid expand={<UserEdit />}>
            <TextField source="id" />
            <TextField source="name" />
            <TextField source="role" />
        </Datagrid>
    </List>
)

Datagrid Inline Editing

The expansion panel breaks the traditional table layout to offer inline editing user experiences. We can't wait to see what you will do with this feature!

Datagrid Row Override

The <Datagrid> component renders a list of records using material-ui's <Table> element - and other components like <TableRow>, <TableHeader>, <TableCell>, etc. In fact, these components have so many options that it's not possible to expose them all in <Datagrid>. To let developers customize absolutely everything about the <Datagrid>, we've added the ability to override the subcomponents used for rendering.

Classic Datagrid

By default, <Datagrid> renders its body using <DatagridBody>, an internal react-admin component. You can pass a custom component as the body prop to override that default. And by the way, <DatagridBody> has a row property set to <DatagridRow> by default for the same purpose. <DatagridRow> receives the row record, the resource, and a copy of the datagrid children. That means you can create a custom datagrid logic without copying several components from the react-admin source.

For instance, to show the selection checkbox only for records that have a status field not set to Pending, you can override <DatagridRow> and <DatagridBody> as follows:

// in src/ReviewList.js
import { Datagrid, DatagridBody, List, TextField } from 'react-admin';
import TableCell from '@material-ui/core/TableCell';
import TableRow from '@material-ui/core/TableRow';
import Checkbox from '@material-ui/core/Checkbox';

const MyDatagridRow = ({ record, resource, id, onToggleItem, children, selected, basePath }) => (
    <TableRow key={id}>
        {/* first column: selection checkbox */}
        <TableCell padding="none">
            {record.status !== 'pending' && <Checkbox
                checked={selected}
                onClick={() => onToggleItem(id)}
            />}
        </TableCell>
        {/* data columns based on children */}
        {React.Children.map(children, field => (
            <TableCell key={`${id}-${field.props.source}`}>
                {React.cloneElement(field, {
                    record,
                    basePath,
                    resource,
                })}
            </TableCell>
        ))}
    </TableRow>
)

const MyDatagridBody = props => <DatagridBody {...props} row={<MyDatagridRow />} />;
const MyDatagrid = props => <Datagrid {...props} body={<MyDatagridBody />} />;

const ReviewList = props => (
    <List {...props}>
        <MyDatagrid>
            ...
        </MyDatagrid>
    </List>
)

export default reviewList;

Datagrid with custom row

More Powerful Row Click

React-admin 2.4 introduced the Datagrid rowClick, a way to make each row clickable without displaying a button. With this new release, rowClick becomes even more powerful, as it can now be a function, in which case it receives the current record as a parameter. That way, the page displayed after the click can depend on some field in the record.

Datagrid row click

For instance, the following row click handler redirects to either edit or show after checking if the record is editable:

const postRowClick = (id, basePath, record) => record.editable ? 'edit' : 'show';

const PostList = props => (
    <List {...props}>
        <Datagrid rowClick={postRowClick}>
            ...
        </Datagrid>
    </List>
);

The function can even return a Promise, allowing you to check an external API before returning a path:

import fetchUserRights from './fetchUserRights';
const postRowClick = (id, basePath, record) => fetchUserRights().then(({ canEdit }) canEdit ? 'edit' : 'show');

Hiding the Export Button

Every List view displays an Export button by default. You can customize the shape of the exported data, and fetch related data, using the exporter prop, as explained in the documentation. But react-admin 2.6 now allows you to disable the export feature altogether, simply by passing false to the exporter prop:

const PostList = props => (
    <List {...props} exporter={false}>
        ...
    </List>
);
List with export button List without export button
list with export list without export

If for whatever reason, you don't want to let users download the data they see as a CSV file, now you can!

Datagrid Skeleton

On slow networks, the previous Datagrid user experience could let users think of a bug when the screen rendered nothing while loading. React-admin 2.6 now shows a skeleton datagrid during the first load instead of nothing.

List Datagrid skeleton

Note that this only affects the first load. On subsequent renderings of the datagrid, react-admin still displays stale data while fetching up-to-date data (that's optimistic rendering).

The skeleton tries to minimize screen flickering by displaying checkboxes only when bulk actions are on, by showing as many columns as the datagrid has children, etc.

Incidentally, this also solves a UI problem that I used to think of "the lone record". When a user opens the app directly in a record detail page and goes back to the list, the list used to display only that record while loading the first page. Now it displays the skeleton instead.

Skeleton after edition

Form Autofocus

HTML inputs support the autoFocus prop, to force the focus on a form element on page load. React-admin lets developers set that props directly on Input components - usually the first of the form:

const PostEdit = props => (
    <Edit {...props}>
        <SimpleForm>
            <TextInput source="title" autoFocus />
            <DateInput source="publication_date" />
            ...
        </SimpleForm>
    </Edit>
)

React-admin can't force the autofocus on a particular input if there is none - it would imply too much magic, and wouldn't work in many cases. Yet, we encourage developers to set autoFocus on the first input of most edition forms. React-admin now follows this guideline in the LoginForm, and in the examples.

Login autofocus

Export Button Disable

A good User Interface properly handles at least 4 states: loaded, loading, empty, and error. Guided by this principle, we're improving the react-admin UI little by little. An example of such improvement is the Export button on empty lists.

When a user filters a list so much that there is no result, or when they simply display an empty list, the react-admin UI used to show an Export button that didn't make sense. Starting with react-admin 2.6, the export button is now disabled in such cases. Not only does it draw less attention from the user, but it also doesn't trigger a troublesome bug.

Disabled export

Edit Toolbar Background

Sometimes very small visual changes improve the user experience a lot. That's what we think of a minor change from 2.5, the background color of the Edit form toolbar:

Before After
before after

The Save and Delete buttons don't float anymore, and the end of the form input zone is clearly underlined. Early feedback from end users tells us they appreciate.

Better Login Page

The login page background is a random image from Unsplash, an online collection of free to use and beautiful pictures. But too much randomness is sometimes distractive. In react-admin 2.5, the random background image only changes once a day.

Incidentally, this image now loads in a non-blocking way, which means that the page will be responsive even before the image finishes loading. This is particularly interesting in e2e tests because a test framework like Cypress.io no longer needs to wait for the background image to start filling the form. In our e2e test suite, it helped reduce the overall test duration by about 10 seconds, which is great!

One last addition: The Login page now uses any custom theme passed to the <Admin> element.

custom login theme

Custom Button Icons

React-admin exposes many button components: SaveButton, CloneButton, DeleteButton, etc. Each of these buttons use a particular material design icon. But sometimes, developers reuse these buttons for something a little bit different. For instance, in a list of messages, saving actually means sending the message. In a list of change, deleting actually means rejecting the change.

To let developers display buttons that stick to the user domain, you can now customize both the label and the icon of all the react-admin buttons.

import RejectIcon from '@material-ui/icons/ThumbsDown';
import { DeleteButton } from 'react-admin';

const RejectButton = props => (
    <DeleteButton label="Reject" icon={<RejectIcon />} />
);

ReferenceManyField Pagination

The <ReferenceManyfield> component is very useful to represent a list of linked records, e.g. a list of reviews on an item in an e-commerce shop. And the most common use is to combine it with <Datagrid>, so that the related records appear in a table.

With react-admin 2.5, you can now paginate this list. That means that you can add a pagination component (for instance react-admin's <Pagination>) to display pagination controls. No need to change anything in the data provider.

import { Pagination } from 'react-admin';

<ReferenceManyField pagination={<Pagination />} reference="reviews" target="post_id">
   ...
</ReferenceManyField>

referenceManyField pagination

Miscellaneous Changes

There are more changes in react-admin 2.5 and 2.6 than what can fit in a blog post. Without getting into details, here is a list of minor enhancements:

  • Add support for arrays of references in exporter fetchRelatedRecords (#2461)
  • Add ability to extend the buttons onClick handlers (2640)
  • Add the ability to disable the /login route and component (2622)
  • Add enableReducers and renderProp options to <TestContext> to help with integration testing (2614)
  • Add ability to disable some options in <SelectInput> (#2555)

You can read the full changelog for 2.5 and 2.6 in the CHANGELOG.md file, in the project source code.

Conclusion

React-admin 2.6 is a solid release, giving even more control to the developer, and improving the user experience. It is backward compatible with the 2.x branch, so don't delay the upgrade!

And as always, if you find a regression in this release, please don't hesitate to open an issue in the react-admin bug tracker on GitHub.