React Admin v3: Zoom in the Styling Layer

François Zaninotto
François ZaninottoOctober 10, 2019
#react#react-admin#tutorial

Developers of react-admin apps need to add custom styles or customize the app theme regularily. Even though react-admin v3 isn't released as stable yet, it's a good time to look about what changes for these common tasks.

Customizing Component CSS

In react-admin v2, we encouraged using material-ui's withStyles HOC to inject custom CSS classes:

import React from "react";
import { withStyles } from "@material-ui/core/styles";
import LaunchIcon from "@material-ui/icons/Launch";

const styles = {
  link: {
    textDecoration: "none",
  },
  icon: {
    width: "0.5em",
    paddingLeft: 2,
  },
};

const MyUrlField = ({ record = {}, source, classes }) => (
  <a href={record[source]} className={classes.link}>
    {record[source]}
    <LaunchIcon className={classes.icon} />
  </a>
);

export default withStyles(styles)(MyUrlField);

React-admin v3 uses material-ui v4+, and that new version offers a hook-based alternative called makeStyles, which returns a hook to be used in a component at runtime:

import React from 'react';
import { makeStyles } from '@material-ui/core';
import LaunchIcon from '@material-ui/icons/Launch';

const useStyles = makeStyles({
    link: {
        textDecoration: 'none',
    },
    icon: {
        width: '0.5em',
        paddingLeft: 2,
    },
});

const MyUrlField = ({ record = {}, source }) => {
    const classes = useStyles();
    return (
        <a href={record[source]} className={classes.link}>
            {record[source]}
            <LaunchIcon className={classes.icon} />
        </a>;
    );
}

export default MyUrlField;

If you prefer the styled-components API, material-ui provides a styled utility for that, too.

import React from 'react';
import { styled } from '@material-ui/core';
import LaunchIcon from '@material-ui/icons/Launch';

const MyLink = styled('a')({
    link: {
        textDecoration: 'none',
    },
    icon: {
        width: '0.5em',
        paddingLeft: 2,
    },
});

const MyUrlField = ({ record = {}, source }) => (
    <MyLink href={record[source]} className={classes.link}>
        {record[source]}
        <LaunchIcon className={classes.icon} />
    </MyLink>;
);

export default MyUrlField;

Under the hood, both makeStyles and styled use a CSS-in-JS framework called JSS. These utilities feel more idiomatic with modern React than the withStyles HOC, and they don't pollute the React tree with additional components.

If you prefer using other CSS-in-JS solutions like Emotion or Glamor, check the material-ui documentation on style library interoperability.

Overriding react-admin Component Styles

Nothing has changed between v2 and v3 for the ability to override react-admin component styles for the root component: you can still pass a classes prop overriding the base styles of the component. Except that in v3, these classes come from the makeStyles return hook.

import { DeleteButton } from "react-admin";
import { makeStyles } from "@material-ui/core";

const useStyles = makeStyles(theme => ({
  deleteButton: {
    color: theme.palette.primary.main,
  },
}));

const MyButton = props => {
  const classes = useStyles();
  return <Button classes={classes} {...props} />;
};

You'll need to dig in the react-admin source code to determine which keys you can override (like the deleteButton key above), but don't worry, we've done our best to make the code super readable.

Responsive Components

To display a different component for desktop on mobile, react-admin v2 offered a <Responsive> component:

import React from "react";
import {
  List,
  Responsive,
  SimpleList,
  Datagrid,
  TextField,
  ReferenceField,
  EditButton,
} from "react-admin";

export const PostList = props => (
  <List {...props}>
    <Responsive
      small={
        <SimpleList
          primaryText={record => record.title}
          secondaryText={record => `${record.views} views`}
          tertiaryText={record =>
            new Date(record.published_at).toLocaleDateString()
          }
        />
      }
      medium={
        <Datagrid>
          <TextField source="id" />
          <ReferenceField label="User" source="userId" reference="users">
            <TextField source="name" />
          </ReferenceField>
          <TextField source="title" />
          <TextField source="body" />
          <EditButton />
        </Datagrid>
      }
    />
  </List>
);

This component is no longer necessary in v3, as material-ui provides a useMediaQuery hook for the same purpose:

import React from "react";
import { useMediaQuery } from "@material-ui/core";
import {
  List,
  SimpleList,
  Datagrid,
  TextField,
  ReferenceField,
  EditButton,
} from "react-admin";

export const PostList = props => {
  const isSmall = useMediaQuery(theme => theme.breakpoints.down("sm"));
  return (
    <List {...props}>
      {isSmall ? (
        <SimpleList
          primaryText={record => record.title}
          secondaryText={record => `${record.views} views`}
          tertiaryText={record =>
            new Date(record.published_at).toLocaleDateString()
          }
        />
      ) : (
        <Datagrid>
          <TextField source="id" />
          <ReferenceField label="User" source="userId" reference="users">
            <TextField source="name" />
          </ReferenceField>
          <TextField source="title" />
          <TextField source="body" />
          <EditButton />
        </Datagrid>
      )}
    </List>
  );
};

Conclusion

React-admin v3 actually does less than react-admin v2 as far as styling is concerned, because material-ui v4 does more. And all the styling logic now takes place in hooks, which means you can easily factor that code to make it reusable across several components.

If that motivates you to go and test react-admin v3 beta, check the upgrade guide. Feel free to report any issues you'd have with this new version in the GitHub issue tracker.

Did you like this article? Share it!