- Admin-on-rest Renamed to React-Admin
restClient
Prop Renamed TodataProvider
in<Admin>
Component- Default REST Clients Moved to Standalone Packages
authClient
Prop Renamed ToauthProvider
in<Admin>
Component- Default (English) Messages Moved To Standalone Package
- Message Hash Main Key Changed ("aor" => "ra")
- Removed the Delete view in Resource
- Replaced
messages
byi18nProvider
in<Admin>
crudSaga
renamed toadminSaga
<AutocompleteInput>
no longer accepts afilter
prop<Datagrid>
No Longer Acceptsoptions
,headerOptions
,bodyOptions
, androwOptions
props<DateInput>
Stores a Date String Instead Of a Date Object- Removed
<DateInput>
options
props <SelectArrayInput>
does not support autocompletion anymore.- CSS Classes Changed
addField
Prop Replaced ByaddField
HOC- No More
refresh
Prop Passed To<List>
Actions - Customizing styles
- Authentication:
<Restricted>
renamed to<Authenticated>
- Authorization:
<WithPermission>
and<SwitchPermissions>
replaced by<WithPermissions>
- Custom Layouts
- Menu
onMenuTap
prop has been renamedonMenuClick
- Logout is now displayed in the AppBar on desktop
- Data providers should support two more types for bulk actions
- react-admin addon packages renamed with ra prefix and moved into root repository
aor-dependent-input
Was Removed- The require,number and email validators should be renamed to require(),number() and validation()
We've chosen to remove term REST from the project name, to emphasize the fact that it can adapt to any type of backend - including GraphQL.
So the main package name has changed from admin-on-rest
to react-admin
. You must update your dependencies:
npm uninstall admin-on-rest
npm install react-admin
As well as all your files depending on the 'admin-on-rest' package:
- import { BooleanField, NumberField, Show } from 'admin-on-rest';
+ import { BooleanField, NumberField, Show } from 'react-admin';
A global search and replace on the string "admin-on-rest" should do the trick in no time.
In the <Admin>
component, the restClient
prop is now called dataProvider
:
import restClient from './restClient';
- <Admin restClient={restClient}>
+ <Admin dataProvider={restClient}>
...
</Admin>
The signature of the Data Provider function is the same as the REST client function, so you shouldn't need to change anything in your previous REST client function.
Once again, this change de-emphasizes the "REST" term in admin-on-rest.
simpleRestClient
and jsonServerRestClient
are no longer part of the core package. They have been moved to standalone packages, where they are the default export:
simpleRestClient
=>ra-data-simple-rest
jsonServerRestClient
=>ra-data-json-server
Update your import
statements accordingly:
- import { simpleRestClient } from 'admin-on-rest';
+ import simpleRestClient from 'ra-data-simple-rest';
- import { jsonServerRestClient } from 'admin-on-rest';
+ import jsonServerRestClient from 'ra-data-json-server';
In the <Admin>
component, the authClient
prop is now called authProvider
:
- import authClient from './authClient';
+ import authProvider from './authProvider';
- <Admin authClient={authClient}>
+ <Admin authProvider={authProvider}>
...
</Admin>
The signature of the authorizations provider function is the same as the authorizations client function, so you shouldn't need to change anything in your previous authorizations client function.
The English messages have moved to another package, ra-language-english
. The core package still displays the interface messages in English by default (by using ra-language-english
as a dependency), but if you overrode some of the messages, you'll need to update the package name:
- import { enMessages } from 'admin-on-rest';
+ import enMessages from 'ra-language-english';
const messages = { 'en': enMessages };
The main key of translation message objects was renamed from "aor" ro "ra". You must update your custom messages accordingly if you overrode core interface messages. If you're a language package author, you must also update and republish your package to have it work with react-admin 2.0.
module.exports = {
- aor: {
+ ra: {
action: {
delete: 'Delete',
show: 'Show',
...
Admin-on-rest used to have a special Delete view, accessible with a special URL, to display a confirmation message after a user clicked on the Delete button. This view added complexity to the early stages of development with admin-on-rest. Besides, it provided a mediocre user experience.
In react-admin, the deletion confirmation is now a Dialog that opens on top of the page where the user currently is.
As a consequence, you no longer need to pass a value to the remove
prop in Resources:
- <Resource name="posts" list={PostList} edit={PostEdit} show={PostShow} remove={Delete} />
+ <Resource name="posts" list={PostList} edit={PostEdit} show={PostShow} />
That also means that if you disabled deletion on a Resource by not passing a remove
prop, you will be surprised by Delete buttons popping in the Edit views. The way to remove this button is to Customize the Edit Toolbar.
In admin-on-rest, localization messages were passed as an object literal in the messages
props of the <Admin>
component. To do the same in react-admin, you must now use a slightly more lengthy syntax, and pass a function in the i18nProvider
prop instead.
- import { Admin, enMessages } from 'admin-on-rest';
- import frMessages from 'aor-language-french';
+ import { Admin } from 'react-admin';
+ import enMessages from 'ra-language-english';
+ import frMessages from 'ra-language-french';
const messages = {
en: enMessages,
fr: frMessages,
};
- const App = () => <Admin locale="en" messages={messages} />;
+ const i18nProvider = locale => messages[locale];
+ const App = () => <Admin locale="en" i18nProvider={i18nProvider} />;
The new i18nProvider
allows to load the messages asynchronously - see the i18nProvider
documentation for details.
If you don't use the <Admin>
component, but prefer to implement your administration inside another root component, you probably followed the Custom App documentation, and used the crudSaga
. This property was renamed to adminSaga
// in src/App.js
- import { crudSaga, ... } from 'admin-on-rest';
+ import { adminSaga, ... } from 'react-admin';
// ...
- sagaMiddleware.run(crudSaga(dataProvider, i18nProvider));
+ sagaMiddleware.run(adminSaga(dataProvider, authProvider, i18nProvider));
Material-ui's implementation of the autocomplete input has radically changed. React-admin maintains backwards compatibility, except for the filter
prop, which no longer makes sense in the new implementation.
Material-ui's implementation of the <Table>
component has reduced dramatically. Therefore, all the advanced features of the datagrid are no longer available from react-admin.
If you need a fixed header, row hover, multi-row selection, or any other material-ui 0.x <Table>
feature, you'll need to implement your own <Datagrid>
alternative, e.g. using the library recommended by material-ui, DevExtreme React Grid.
The value of the <DateInput>
used to be a Date
object. It's now a String
, i.e. a stringified date. If you used format
and parse
to convert a string to a Date
, you can now remove these props:
- const dateFormatter = v => { // from record to input
- // v is a string of "YYYY-MM-DD" format
- const match = /(\d{4})-(\d{2})-(\d{2})/.exec(v);
- if (match === null) return;
- const d = new Date(match[1], parseInt(match[2], 10) - 1, match[3]);
- if (isNaN(d)) return;
- return d;
- };
- const dateParser = v => { // from input to record
- // v is a `Date` object
- if (!(v instanceof Date) || isNaN(v)) return;
- const pad = '00';
- const yy = v.getFullYear().toString();
- const mm = (v.getMonth() + 1).toString();
- const dd = v.getDate().toString();
- return `${yy}-${(pad + mm).slice(-2)}-${(pad + dd).slice(-2)}`;
- };
- <DateInput source="isodate" format={dateFormatter} parse={dateParser} label="ISO date" />
+ <DateInput source="isodate" label="ISO date" />
On the other way around, if your data provider expects JavaScript Date
objects for value, you now need to do the conversion to and from strings using format
and parse
:
- <DateInput source="isodate" label="ISO date" />
+ const dateFormatter = v => { // from record to input
+ // v is a `Date` object
+ if (!(v instanceof Date) || isNaN(v)) return;
+ const pad = '00';
+ const yy = v.getFullYear().toString();
+ const mm = (v.getMonth() + 1).toString();
+ const dd = v.getDate().toString();
+ return `${yy}-${(pad + mm).slice(-2)}-${(pad + dd).slice(-2)}`;
+ };
+ const dateParser = v => { // from input to record
+ // v is a string of "YYYY-MM-DD" format
+ const match = /(\d{4})-(\d{2})-(\d{2})/.exec(v);
+ if (match === null) return;
+ const d = new Date(match[1], parseInt(match[2], 10) - 1, match[3]);
+ if (isNaN(d)) return;
+ return d;
+ };
+ <DateInput source="isodate" format={dateFormatter} parse={dateParser} label="ISO date" />
Material-ui 1.0 doesn't provide a real date picker, so the options
prop of the <DateInput>
is no longer supported.
This component relied on material-ui-chip-input which is not yet fully ported to Material-ui 1.0: it doesn't support the autocomplete feature we need. We will add another component for this when material-ui-chip-input
is ported.
React-admin does not rely heavily on CSS classes. Nevertheless, a few components added CSS classes to facilitate per-field theming: <SimpleShowLayout>
, <Tab>
, and <FormInput>
. These CSS classes used to follow the "aor-" naming pattern. They have all been renamed to use the "ra-" pattern instead. Here is the list of concerned classes:
aor-field
=>ra-field
aor-field-[source]
=>ra-field-[source]
aor-input
=>ra-input
aor-input-[source]
=>ra-input-[source]
If you used CSS to customize the look and feel of these components, please update your CSS selectors accordingly.
Adding the addField
prop to a component used to automatically add a redux-form <Field>
component around an input component that you wanted to bind to the edit or create form. This feature was moved to a Higher-order component (HOC):
import SelectField from '@material-ui/core/SelectField';
import MenuItem from '@material-ui/core/MenuItem';
+ import { addField } from 'react-admin';
const SexInput = ({ input, meta: { touched, error } }) => (
<SelectField
floatingLabelText="Sex"
errorText={touched && error}
{...input}
>
<MenuItem value="M" primaryText="Male" />
<MenuItem value="F" primaryText="Female" />
</SelectField>
);
- SexInput.defaultProps = {
- addField: true, // require a <Field> decoration
- }
- export default SexInput;
+ export default addField(SexInput);
Admin-on-rest input components all use the new addField
HOC. This means that it's no longer necessary to set the addField
prop when you compose one of admin-on-rest's components:
- import { SelectInput } from 'admin-on-rest';
+ import { SelectInput } from 'react-admin';
const choices = [
{ id: 'M', name: 'Male' },
{ id: 'F', name: 'Female' },
]
const SexInput = props => <SelectInput {...props} choices={choices}/>;
- SexInput.defaultProps = {
- addField: true;
- }
export default SexInput;
The Refresh button now uses Redux to force a refetch of the data. As a consequence, the List view no longer passes the refresh
prop to the <Actions>
component. If you relied on that prop to refresh the list, you must now use the new <RefreshButton>
component.
import CardActions from '@material-ui/core/CardActions';
- import FlatButton from '@material-ui/core/FlatButton';
- import { CreateButton } from 'admin-on-rest';
- import NavigationRefresh from '@material-ui/core/svg-icons/navigation/refresh';
+ import { CreateButton, RefreshButton } from 'react-admin';
- const PostListActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter, refresh }) => (
+ const PostListActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter }) => (
<CardActions>
{filters && React.cloneElement(filters, { resource, showFilter, displayedFilters, filterValues, context: 'button' }) }
<CreateButton basePath={basePath} />
- <FlatButton primary label="refresh" onClick={refresh} icon={<NavigationRefresh />} />
+ <RefreshButton />
</CardActions>
);
Following the same path as Material UI, react-admin now uses JSS for styling components instead of the style
prop. This approach has many benefits, including a smaller DOM, faster rendering, media queries support, and automated browser prefixing.
All react-admin components now accept a className
prop instead of the elStyle
prop. But it expects a CSS class name instead of a CSS object. To set custom styles through a class name, you must use the withStyles
Higher Order Component supplied by Material-UI.
- import { EmailField, List, Datagrid } from 'admin-on-rest';
- const UserList = props => (
- <List {...props}>
- <Datagrid>
- ...
- <EmailField source="email" elStyle={{ textDecoration: 'none' }} />
- </Datagrid>
- </List>
- );
- export default UserList;
// renders in the datagrid as
//<td>
// <a style="text-decoration:none" href="mailto:[email protected]">[email protected]</a>
//</td>
+ import { EmailField, List, Datagrid } from 'react-admin';
+ import { withStyles } from '@material-ui/core/styles';
+ const styles = {
+ field: {
+ textDecoration: 'none',
+ },
+ };
+ const UserList = ({ classes, ...props }) => (
+ <List {...props}>
+ <Datagrid>
+ ...
+ <EmailField source="email" className={classes.field} />
+ </Datagrid>
+ </List>
+ );
+ export default withStyles(styles)(UserList);
In addition to elStyle
, Field and Input components used to support a style
prop to override the styles of the container element (the <td>
in a datagrid). This prop is no longer supported in react-admin. Instead, the Datagrid
component will check if its children have a headerClassName
and cellClassName
props. If they do, it will apply those classes to the table header and cells respectively.
- import { EmailField, List, Datagrid } from 'admin-on-rest';
+ import { EmailField, List, Datagrid } from 'react-admin';
+ import { withStyles } from '@material-ui/core/styles';
+ const styles = {
+ cell: {
+ backgroundColor: 'lightgrey',
+ },
+ field: {
+ textDecoration: 'none',
+ },
+ };
- const UserList = props => (
+ const UserList = ({ classes, ...props }) => (
<List {...props}>
<Datagrid>
- <EmailField source="email" style={{ backgroundColor: 'lightgrey' }} elStyle={{ textDecoration: 'none' }} />
+ <EmailField source="email" cellClassName={classes.cell} className={classes.field} />
</Datagrid>
</List>
);
- export default UserList;
+ export default withStyles(styles)(UserList);
// renders in the datagrid as
// <td style="background-color:lightgrey">
// <a style="text-decoration:none" href="mailto:[email protected]">
// [email protected]
// </a>
// </td>
Furthermore, some React-admin components such as the List
, Filter
, and Datagrid
also accept a classes
prop. This prop is injected by the withStyles
Higher Order Component and allows you to customize the style of some deep children. See the Theming documentation for details.
Tip: When you set the classes
prop in the List
or Datagrid
components, you might see warnings about the cell
and field
classes being unknown by those components. Those warnings are not displayed in production
mode, and are just a way to ensure you know what you're doing. And you can make them disappear by destructuring the classes
prop:
import { EmailField, List, Datagrid } from 'react-admin';
import { withStyles } from '@material-ui/core/styles';
const styles = {
header: { fontWeight: 'bold' },
actions: { fontWeight: 'bold' },
emailCellClassName: {
backgroundColor: 'lightgrey',
},
emailFieldClassName: {
textDecoration: 'none',
},
};
export const UserList = ({
classes: { emailCellClassName, emailFieldClassName, ...classes },
...props
}) => (
<List
{...props}
filters={<UserFilter />}
sort={{ field: 'name', order: 'ASC' }}
classes={classes}
>
<Datagrid>
<EmailField
source="email"
cellClassName={emailCellClassName}
className={emailFieldClassName}
/>
</Datagrid>
</List>
);
// renders in the datagrid as
<td style="background-color:lightgrey">
<a style="text-decoration:none" href="mailto:[email protected]">
[email protected]
</a>
</td>
Finally, Field and Input components accept a textAlign
prop, which can be either left
, or right
. Through this prop, these components inform their parent component that they look better when aligned to left or right. It's the responsability of the parent component to apply this alignment. For instance, the NumberField
component has a default value of right
for the textAlign
prop, so the Datagrid
component uses a right alignment in header and table cell - but form components (SimpleForm
and TabbedForm
) ignore the prop and display it left aligned.
The Restricted
component has been renamed to Authenticated
. Update your import
statements accordingly:
// in src/MyPage.js
import { withRouter } from 'react-router-dom';
- import { Restricted } from 'admin-on-rest';
+ import { Authenticated } from 'react-admin';
const MyPage = ({ location }) => (
- <Restricted authParams={{ foo: 'bar' }} location={location}>
+ <Authenticated authParams={{ foo: 'bar' }} location={location}>
<div>
...
</div>
- </Restricted>
+ </Authenticated>
)
export default withRouter(MyPage);
We removed the WithPermission
and SwitchPermissions
in favor of a more versatile component: WithPermissions
. The WithPermissions
component retrieves permissions by calling the authProvider
with the AUTH_GET_PERMISSIONS
type. It then passes the permissions to the render callback.
This component follows the render callback pattern. Just like the React Router Route
component, you can pass a render callback to <WithPermissions>
either as its only child, or via its render
prop (if both are passed, the render
prop is used).
If you were using WithPermission
before, here's how to migrate to WithPermissions
:
import React from 'react';
- import { MenuItemLink, WithPermission } from 'admin-on-rest';
+ import { MenuItemLink, WithPermissions } from 'react-admin';
export default ({ onMenuClick, logout }) => (
<div>
<MenuItemLink to="/posts" primaryText="Posts" onClick={onMenuClick} />
<MenuItemLink to="/comments" primaryText="Comments" onClick={onMenuClick} />
- <WithPermission value="admin">
- <MenuItemLink to="/custom-route" primaryText="Miscellaneous" onClick={onMenuClick} />
- </WithPermission>
+ <WithPermissions>
+ {({ permissions }) => permissions === 'admin'
+ ? <MenuItemLink to="/custom-route" primaryText="Miscellaneous" onClick={onMenuClick} />
+ : null
+ }
+ </WithPermissions>
{logout}
</div>
);
If you were using SwitchPermissions
before, here's how to migrate to WithPermissions
:
// before
import React from 'react';
import BenefitsSummary from './BenefitsSummary';
import BenefitsDetailsWithSensitiveData from './BenefitsDetailsWithSensitiveData';
- import { ViewTitle, SwitchPermissions, Permission } from 'admin-on-rest';
+ import { ViewTitle, WithPermissions } from 'react-admin';
export default () => (
<div>
- <SwitchPermissions>
- <Permission value="associate">
- <BenefitsSummary />
- </Permission>
- <Permission value="boss">
- <BenefitsDetailsWithSensitiveData />
- </Permission>
- </SwitchPermissions>
+ <WithPermissions>
+ {({ permissions }) => {
+ if (permissions === 'associate') {
+ return <BenefitsSummary />;
+ }
+ if (permissions === 'boss') {
+ return <BenefitsDetailsWithSensitiveData />;
+ }
+ }}
+ </WithPermissions>
</div>
);
We also reviewed how permissions are passed to the List
, Edit
, Create
, Show
and Delete
components. React-admin now injects the permissions to theses components in the permissions
props, without having to use the render callback pattern. It should now be easier to customize behaviors and components according to permissions.
Here's how to migrate a Create
component:
const UserCreateToolbar = ({ permissions, ...props }) =>
<Toolbar {...props}>
<SaveButton
label="user.action.save_and_show"
redirect="show"
submitOnEnter={true}
/>
{permissions === 'admin' &&
<SaveButton
label="user.action.save_and_add"
redirect={false}
submitOnEnter={false}
variant="flat"
/>}
</Toolbar>;
- export const UserCreate = ({ ...props }) =>
+ export const UserCreate = ({ permissions, ...props }) =>
<Create {...props}>
- {permissions =>
<SimpleForm
toolbar={<UserCreateToolbar permissions={permissions} />}
defaultValue={{ role: 'user' }}
>
<TextInput source="name" validate={[required()]} />
{permissions === 'admin' &&
<TextInput source="role" validate={[required()]} />}
</SimpleForm>
- }
</Create>;
Here's how to migrate an Edit
component:
// before
- export const UserEdit = ({ ...props }) =>
+ export const UserEdit = ({ permissions, ...props }) =>
<Edit title={<UserTitle />} {...props}>
- {permissions =>
<TabbedForm defaultValue={{ role: 'user' }}>
<FormTab label="user.form.summary">
{permissions === 'admin' && <DisabledInput source="id" />}
<TextInput source="name" validate={required()} />
</FormTab>
{permissions === 'admin' &&
<FormTab label="user.form.security">
<TextInput source="role" validate={required()} />
</FormTab>}
</TabbedForm>
- }
</Edit>;
Here's how to migrate a List
component. Note that the <Filter>
component does not support the function as a child pattern anymore. If you need permissions within it, just pass them from the List
component.
- const UserFilter = ({ ...props }) =>
+ const UserFilter = ({ permissions, ...props }) =>
<Filter {...props}>
- {permissions => [
<TextInput
key="user.list.search"
label="user.list.search"
source="q"
alwaysOn
/>,
<TextInput key="name" source="name" />,
permissions === 'admin' ? <TextInput source="role" /> : null,
- ]}
</Filter>;
- export const UserList = ({ ...props }) =>
+ export const UserList = ({ permissions, ...props }) =>
<List
{...props}
- filters={<UserFilter />}
+ filters={<UserFilter permissions={permissions} />}
sort={{ field: 'name', order: 'ASC' }}
>
- {permissions =>
<Responsive
small={
<SimpleList
primaryText={record => record.name}
secondaryText={record =>
permissions === 'admin' ? record.role : null}
/>
}
medium={
<Datagrid>
<TextField source="id" />
<TextField source="name" />
{permissions === 'admin' && <TextField source="role" />}
{permissions === 'admin' && <EditButton />}
<ShowButton />
</Datagrid>
}
/>
- }
</List>;
Moreover, you won't need the now deprecated <WithPermission>
or <SwitchPermissions>
components inside a Dashboard
to access permissions anymore: react-admin injects permissions
to the dashboard, too:
// in src/Dashboard.js
import React from 'react';
import BenefitsSummary from './BenefitsSummary';
import BenefitsDetailsWithSensitiveData from './BenefitsDetailsWithSensitiveData';
- import { ViewTitle, SwitchPermissions, Permission } from 'admin-on-rest';
+ import { ViewTitle } from 'react-admin';
- export default () => (
+ export default ({ permissions }) => (
<Card>
<ViewTitle title="Dashboard" />
- <SwitchPermissions>
- <Permission value="associate">
- <BenefitsSummary />
- </Permission>
- <Permission value="boss">
- <BenefitsDetailsWithSensitiveData />
- </Permission>
- </SwitchPermissions>
+ {permissions === 'associate' && <BenefitsSummary />}
+ {permissions === 'boss' && <BenefitsDetailsWithSensitiveData />}
</Card>
);
Finally, you won't need the now deprecated <WithPermission>
or <SwitchPermissions>
in custom routes either if you want access to permissions. Much like you can restrict access to authenticated users only with the Authenticated
component, you may decorate your custom route with the WithPermissions
component. It will ensure the user is authenticated and call the authProvider
with the AUTH_GET_PERMISSIONS
type and the authParams
you specify:
{% raw %}
// in src/MyPage.js
import React from 'react';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import { ViewTitle, WithPermissions } from 'react-admin';
import { withRouter } from 'react-router-dom';
const MyPage = ({ permissions }) => (
<Card>
<ViewTitle title="My custom page" />
<CardContent>Lorem ipsum sic dolor amet...</CardContent>
{permissions === 'admin'
? <CardContent>Sensitive data</CardContent>
: null
}
</Card>
)
const MyPageWithPermissions = ({ location, match }) => (
<WithPermissions
authParams={{ key: match.path, params: route.params }}
// location is not required but it will trigger a new permissions check if specified when it changes
location={location}
render={({ permissions }) => <MyPage permissions={permissions} /> }
/>
);
export default MyPageWithPermissions;
// in src/customRoutes.js
import React from 'react';
import { Route } from 'react-router-dom';
import Foo from './Foo';
import Bar from './Bar';
import Baz from './Baz';
import MyPageWithPermissions from './MyPage';
export default [
<Route exact path="/foo" component={Foo} />,
<Route exact path="/bar" component={Bar} />,
<Route exact path="/baz" component={Baz} noLayout />,
<Route exact path="/baz" component={MyPageWithPermissions} />,
];
The default layout has been simplified, and this results in a simplified custom layout too. You don't need to pass the AdminRoutes
anymore, as the layout receives the component to render as the standard children
prop:
import React, { createElement, Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
- import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider';
+ import { MuiThemeProvider, withStyles } from '@material-ui/core/styles';
- import CircularProgress from '@material-ui/core/CircularProgress';
import {
- AdminRoutes,
AppBar,
Menu,
Notification,
Sidebar,
- setSidebarVisibility,
- } from 'admin-on-rest';
+ } from 'react-admin';
- const styles = {
- wrapper: {
- // Avoid IE bug with Flexbox, see #467
- display: 'flex',
- flexDirection: 'column',
- },
- main: {
- display: 'flex',
- flexDirection: 'column',
- minHeight: '100vh',
- },
- body: {
- backgroundColor: '#edecec',
- display: 'flex',
- flex: 1,
- overflowY: 'hidden',
- overflowX: 'scroll',
- },
- content: {
- flex: 1,
- padding: '2em',
- },
- loader: {
- position: 'absolute',
- top: 0,
- right: 0,
- margin: 16,
- zIndex: 1200,
- },
- };
+ const styles = theme => ({
+ root: {
+ display: 'flex',
+ flexDirection: 'column',
+ zIndex: 1,
+ minHeight: '100vh',
+ backgroundColor: theme.palette.background.default,
+ position: 'relative',
+ },
+ appFrame: {
+ display: 'flex',
+ flexDirection: 'column',
+ overflowX: 'auto',
+ },
+ contentWithSidebar: {
+ display: 'flex',
+ flexGrow: 1,
+ },
+ content: {
+ display: 'flex',
+ flexDirection: 'column',
+ flexGrow: 2,
+ padding: theme.spacing.unit * 3,
+ marginTop: '4em',
+ paddingLeft: 5,
+ },
+ });
class MyLayout extends Component {
- componentWillMount() {
- this.props.setSidebarVisibility(true);
- }
render() {
const {
children,
- customRoutes,
dashboard,
isLoading,
logout,
menu,
+ open,
title,
} = this.props;
return (
<MuiThemeProvider>
- <div style={styles.wrapper}>
+ <div className={classes.root}>
- <div style={styles.main}>
+ <div className={classes.appFrame}>
- <AppBar title={title} />
+ <AppBar title={title} open={open} logout={logout} />
- <div className="body" style={styles.body}>
+ <main className={classes.contentWithSidebar}>
- <div style={styles.content}>
- <AdminRoutes
- customRoutes={customRoutes}
- dashboard={dashboard}
- >
- {children}
- </AdminRoutes>
<Sidebar>
{createElement(menu || Menu, {
logout,
hasDashboard: !!dashboard,
})}
</Sidebar>
+ <div className={classes.content}>
+ {children}</div>
+ </div>
- </div>
+ </main>
<Notification />
- {isLoading && (
- <CircularProgress
- color="#fff"
- size={30}
- thickness={2}
- style={styles.loader}
- />
- )}
</div>
</div>
</MuiThemeProvider>
);
}
}
MyLayout.propTypes = {
- authClient: PropTypes.func,
+ children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
+ classes: PropTypes.object,
- customRoutes: PropTypes.array,
dashboard: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
- isLoading: PropTypes.bool.isRequired,
+ logout: PropTypes.oneOfType([PropTypes.node PropTypes.func, PropTypes.string]),
menu: PropTypes.element,
+ open: PropTypes.bool,
- resources: PropTypes.array,
- setSidebarVisibility: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
};
const mapStateToProps = state => ({
- isLoading: state.admin.loading > 0
+ open: state.admin.ui.sidebarOpen,
});
- export default connect(mapStateToProps, { setSidebarVisibility })(MyLayout);
+ export default connect(mapStateToProps, {})(withStyles(styles)(MyLayout));
Tip: React-admin's theme is a bot more complex than that, as it is reponsive. Check out the default layout source for details.
Material-ui renamed all xxxTap
props to xxxClick
, so did we.
import React from 'react';
import { connect } from 'react-redux';
- import { MenuItemLink, getResources } from 'admin-on-rest';
+ import { MenuItemLink, getResources } from 'react-admin';
- const Menu = ({ resources, onMenuTap, logout }) => (
+ const Menu = ({ resources, onMenuClick, logout }) => (
<div>
{resources.map(resource => (
- <MenuItemLink to={`/${resource.name}`} primaryText={resource.name} onClick={onMenuTap} />
+ <MenuItemLink to={`/${resource.name}`} primaryText={resource.name} onClick={onMenuClick} />
))}
- <MenuItemLink to="/custom-route" primaryText="Miscellaneous" onClick={onMenuTap} />
+ <MenuItemLink to="/custom-route" primaryText="Miscellaneous" onClick={onMenuClick} />
{logout}
</div>
);
const mapStateToProps = state => ({
resources: getResources(state),
});
export default connect(mapStateToProps)(Menu);
The Logout button is now displayed in the AppBar on desktop, but is still displayed as a menu item on small devices.
This impacts how you build a custom menu, as you'll now have to check whether you are on small devices before displaying the logout:
// in src/Menu.js
import React from 'react';
import { connect } from 'react-redux';
- import { MenuItemLink, getResources } from 'admin-on-rest';
+ import { MenuItemLink, getResources, Responsive } from 'react-admin';
import { withRouter } from 'react-router-dom';
const Menu = ({ resources, onMenuClick, logout }) => (
<div>
{resources.map(resource => (
<MenuItemLink to={`/${resource.name}`} primaryText={resource.name} onClick={onMenuClick} />
))}
<MenuItemLink to="/custom-route" primaryText="Miscellaneous" onClick={onMenuClick} />
- {logout}
+ <Responsive xsmall={logout} medium={null} />
</div>
);
const mapStateToProps = state => ({
resources: getResources(state),
});
export default withRouter(connect(mapStateToProps)(Menu));
It also impacts custom layouts if you're using the default AppBar
. You now have to pass the logout
prop to the AppBar
:
// in src/MyLayout.js
const MyLayout () => ({ logout, ...props }) => (
<MuiThemeProvider>
...
- <AppBar title={title} />
+ <AppBar title={title} logout={logout} />
...
</MuiThemeProvider>
);
The List
component now support bulk actions. The consequence is that data providers should support them too. We introduced two new message types for the dataProvider
: DELETE_MANY
and UPDATE_MANY
.
Both will be called with an ids
property in their params, containing an array of resource ids. In addition, UPDATE_MANY
will also get a data
property in its params, defining how to update the resources.
Please refer to the dataProvider
documentation for more information.
The aor-graphql
and aor-realtime
packages have been migrated into the main react-admin
repository and renamed with the new prefix. Besides, aor-graphql-client
and aor-graphql-client-graphcool
follow the new dataProvider packages naming.
aor-realtime
=>ra-realtime
aor-graphql-client
=>ra-data-graphql
aor-graphql-client-graphcool
=>ra-data-graphcool
Update your import
statements accordingly:
- import realtimeSaga from 'aor-realtime';
+ import realtimeSaga from 'ra-realtime';
- import buildGraphQLProvider from 'aor-graphql-client';
+ import buildGraphQLProvider from 'ra-data-graphql';
- import buildGraphcoolProvider from 'aor-graphql-client-graphcool';
+ import buildGraphcoolProvider from 'ra-data-graphcool';
The aor-dependent-input
package has been removed.
You can achieve a similar effect to the old <DependentInput>
component by using the new <FormDataConsumer>
component.
To display a component based on the value of the current (edited) record, wrap that component with <FormDataConsumer>
, which uses grabs the form data from the redux-form state, and passes it to a child function:
- import { DependentInput } from 'aor-dependent-input';
+ import { FormDataConsumer } from 'react-admin';
export const UserCreate = (props) => (
<Create {...props}>
<SimpleForm>
<TextInput source="firstName" />
<TextInput source="lastName" />
<BooleanInput source="hasEmail" label="Has email ?" />
- <DependentInput dependsOn="hasEmail">
- <TextInput source="email" />
- </DependentInput>
+ <FormDataConsumer>
+ {({ formData, ...rest }) => formData.hasEmail &&
+ <TextInput source="email" {...rest} />
+ }
+ </FormDataConsumer>
</SimpleForm>
</Create>
);
As for the <DependentField>
in a <Show>
view, you need to use an alternative approach, taking advantage of the structure of <Show>
, which in fact decomposes into a controller and a view component:
// inside react-admin
const Show = props => (
<ShowController {...props}>
{controllerProps => <ShowView {...props} {...controllerProps} />}
</ShowController>
);
The <ShowController>
fetches the record
from the data provider, and passes it to its child function when received (among the controllerProps
). That means the following code:
import { Show, SimpleShowLayout, TextField } from 'react-admin';
const UserShow = props => (
<Show {...props}>
<SimpleShowLayout>
<TextField source="username" />
<TextField source="email" />
</SimpleShowLayout>
</Show>
);
Is equivalent to:
import { ShowController, ShowView, SimpleShowLayout, TextField } from 'react-admin';
const UserShow = props => (
<ShowController {...props}>
{controllerProps =>
<ShowView {...props} {...controllerProps}>
<SimpleShowLayout>
<TextField source="username" />
<TextField source="email" />
</SimpleShowLayout>
</ShowView>
}
</ShowController>
);
If you want one field to be displayed based on the record
, for instance to display the email field only if the hasEmail
field is true
, you just need to test the value from controllerProps.record
, as follows:
import { ShowController, ShowView, SimpleShowLayout, TextField } from 'react-admin';
const UserShow = props => (
<ShowController {...props}>
{controllerProps =>
<ShowView {...props} {...controllerProps}>
<SimpleShowLayout>
<TextField source="username" />
{controllerProps.record && controllerProps.record.hasEmail &&
<TextField source="email" />
}
</SimpleShowLayout>
</ShowView>
}
</ShowController>
);
The required
,number
and email
validators must now be executed just like the other validators, not passed as function arguments.
Update your require
,number
and email
validations.
-<TextInput source="foo" validate={[required,maxSize(2)]} />
+<TextInput source="foo" validate={[required(),maxSize(2)]} />
-<TextInput source="foo" validate={number} />
+<TextInput source="foo" validate={number()} />
-<TextInput source="foo" validate={email} />
+<TextInput source="foo" validate={email()} />