Marmelab Blog

Admin-on-rest 1.0 Stable is out

After eight months of hard work, nine iterations and 27 releases to stabilize the component API, I'm finally ready to announce the first stable release of admin-on-rest, the React admin GUI for REST APIs. It's been published to npm today.

Admin-on-rest is Stable

It took that long to make sure the design choices could stand most usage scenarios, and tweak the component API until we're confident that we won't need to break it in the near future. With more unit and functional tests than ever, admin-on-rest is now ready for the "stable" label. You can use it with confidence: future updates won't break backwards compatibility (until version 2.0!). Oh, I almost forgot: admin-on-rest 1.0 comes with a fully updated demo emphasizing best practices, and a complete online documentation.

When you look closely, the result may not look spectacular: only 60 React components, and about 8,600 lines of code. But this code is mostly glue to make awesome libraries (React, material-ui, redux, redux-form, redux-saga, react-router) work together, and let you build admin applications of any level of sophistication.

If you've already been using admin-on-rest before today, you may wonder: What's new in 1.0? Let's list the most important changes:

Upgrade To Latest Versions

Admin-on-rest v1.0 comes with up-to-date dependencies. The most important ones are:

  • React 15.5.4,
  • redux-form 6.6,
  • react-router 4.1,
  • material-ui 0.17,

That means that the latest features and bugfixes of these libraries come right away in admin-on-rest! But sometimes this comes at the price of a Backwards Compatibility (BC) break...

Leveraging Redux-Form Field-Level Validation

Admin-on-rest initially offered a home-made field-level validation system. That's because redux-form only provided validation at the form level until recently. But since version 6.3, redux-form supports field-level validation, so we removed our implementation to use theirs (it's much better).

Starting with 1.0, all validation in admin-on-rest is delegated to redux-form. This means you can do sync and async validation, throw errows or warnings on invalid data. Whenever you use an admin-on-rest Input component, the props not directly required by this component are passed to redux-form's Field component. So to add field-level validation, you just need to pass a validate prop (expecting an array of validator functions).

import { required, minLength, maxLength, minValue, maxValue, number, regex, email, choices } from 'admin-on-rest';

export const UserCreate = (props) => (
    <Create {...props}>
        <SimpleForm>
            <TextInput source="firstName" validate={[ required, minLength(2), maxLength(15) ]} />
            <TextInput source="email" validation={email} />
            <NumberInput source="age" validation={[ number, minValue(18) ]}/>
            <TextInput label="Zip Code" source="zip" validation={regex(/^\d{5}$/, 'Must be a valid Zip Code')}/>
            <SelectInput
                source="sex"
                choices={[
                    { id: 'm', name: 'Male' },
                    { id: 'f', name: 'Female' },
                ]}
                validation={choices(['m', 'f'], 'Must be Male or Female')}
            />
        </SimpleForm>
    </Create>
);

Admin-on-rest already bundles a few validator functions, that you can just require and use as field validators. They are:

  • required if the field is mandatory,
  • minValue to specify a minimum value for integers,
  • maxValue to specify a maximum value for integers,
  • minLength to specify a minimum length for strings,
  • maxLength to specify a maximum length for strings,
  • email to check that the input is a valid email address,
  • regex to validate that the input matches a regex,
  • choices to validate that the input is within a given list,

Validators are just functions returning a string on error, so it shouldn't be too hard to implement your own validators:

const required = value => value ? undefined : 'Required';

Form-level validation is still possible. Check out the form validation documentation for details.

Note: If you used validator functions with version 0.9, this is a BC break. You'll have to upgrade your code.

// before
<TextInput source="age" validation={{ required: true, min: 18 }}/>
// after
<TextInput source="age" validate={[ required, min(18) ]} />

Note: Editors of translation packages for admin-on-rest must now include translations for validation error messages in their dictionaries. Check the French translation upgrade to 1.0 for a list of the new keys.

We Now Use React-Router v4

This internal change won't affect you if you didn't define custom routes. Switching to the newest react-router version (a complete rewrite, for good reasons) opens tons of new possibilities (like simplified server-side rendering), and simplifies routing a great deal.

However, if your application has custom routes, or if you include admin-on-rest in a custom app, then you'll have to change some code.

The customRoutes prop of the <Admin> component now expects an array of routes:

// before
const customRoutes = () => (
    <Route>
        <Route path="/foo" component={Foo} />
        <Route path="/bar" component={Bar} />
    </Route>
);
// after
const customRoutes = [
    <Route exact path="/foo" component={Foo} />,
    <Route exact path="/bar" component={Bar} />,
];

Besides, react-router v4 isn't backwards compatible with v3 (for instance, nested routes don't work the same). Refer to the react-router v4 documentation for details.

If you included admin-on-rest in another app, then you must rewrite this part of your app. Head to the Custom app documentation for details.

The Authentication Logic Moved to Saga

This is another internal refactoring that won't change anything for you if you didn't used authentication in the past. It simplifies the dispatching of auth-related events (which are now actions).

It introduces a new component, <Restricted>, which makes it simple to restrict a piece of content to privileged users only.

// in src/MyPage.js
import { withRouter } from 'react-router-dom';
import { Restricted } from 'admin-on-rest';

const MyPage = ({ location }) =>
    <Restricted location={location} />
        <div>
            ...
        </div>
    </Restricted>
}

export default withRouter(MyPage);

The <Restricted> component calls the authClient function with the AUTH_CHECK verb. If the response is a fulfilled promise, the child component is rendered. If the response is a rejected promise, <Restricted> redirects to the login form. Upon successful login, the user is redirected to the initial location (that's why it's necessary to get the location from the router).

For existing apps, the authentication refactoring changes 2 things:

  1. No need to pass the authClient around in a custom app (see the the Custom app documentation for details)
  2. Your authClient must now handle a new AUTH_ERROR event, and define which HTTP error codes should trigger a logout
// in src/authClient.js
import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR } from 'admin-on-rest';

export default (type, params) => {
    if (type === AUTH_LOGIN) {
        // ...
    }
    if (type === AUTH_LOGOUT) {
        // ...
    }
    if (type === AUTH_ERROR) {
        const { status } = params;
        if (status === 401 || status === 403) {
            localStorage.removeItem('token');
            return Promise.reject();
        }
        return Promise.resolve();
    }
    return Promise.resolve();
};

This logic was previously located in the httpClient - you don't need it there anymore.

All Components Are Available From The Root of The admin-on-rest Package

In previous versions, you had to remember where to require functions and components from. Not anymore with 1.0, where all exported elements are available from the root admin-on-rest package:

// before
import { translate } from 'admin-on-rest';
import { TextInput } from 'admin-on-rest/lib/mui';
// after
import { TextInput, translate } from 'admin-on-rest';

This should simplify development a great deal - and it doesn't even breaks BC! The reason why components were exported from a diffferent place than functions is that we once thought that we could provide admin-on-rest versions for Bootstrap, or other UI Kits. We now know it's too time consuming to even consider such a thing, so no need to force developers using two different namespaces.

New <SelectField> Component

There was a <SelectInput> component, but no <SelectField>. Users really missed the ability to display an enumerated value in List and Show pages. The new <SelectField> fills that gap. It takes the same choice attribute as the <SelectInput> component:

import { SelectField } from 'admin-on-rest';

<SelectField source="gender" choices={[
   { id: 'M', name: 'Male' },
   { id: 'F', name: 'Female' },
]} />

<SelectField> accepts a half dozen of props, all of which are described in the Fields Documentation.

Options Are Now Translated In DropDowns

The <SelectInput>, <AutocompleteInput>, <RadioButtonGroupInput>, and <CheckboxGroupInput> components all display a list of possible choices to fill an input.

Starting with version 1.0, these choices are now automatically translated, except if the component is enclosed in a <ReferenceInput>. Developers of non-English UIs will love that one!

Miscellaneous

There are more additions to 1.0 than what a blog post can detail:

  • Add linkType prop to <ReferenceField> to allow customization or removal of hyperlink on references
  • Add ability to override the <Filter> component by using redux-form's new onChange method
  • Add message in <List> when the REST API returns no result (mantis)
  • Add ability to remove images in <ImageInput>
  • Add error when an erroneous REST response does not contain the error key
  • Add the ability to pass an initial state to createStore (thedersen)
  • Add link from ImageInput documentation to REST Client decoration documentation (leesei)
  • Add documentation for parse() and format() in Inputs reference (leesei)
  • Add warning in documentation about <Resource> required for ReferenceManyField usage

A Huge Community

Version 1.0 also comes at a time where admin-on-rest has found a real developer community. Contributors help us fix bugs and typos, add features and documentation, on a daily basis.

Third-party modules (for languages, rest clients, or custom input components) are published every week. The interface is now translated in 14 languages !

By the way, if you're the author of an open-source package extending admin-on-rest, don't forget to send us a Pull Request so that we can include your package in the documentation. And we advise you to use GitHub's new tagging features to improve the discoverability of your module. The following link already lists 19 repositories of admin-on-rest packages:

https://github.com/search?q=topic%3Aadmin-on-rest+&type=Repositories

A Few Bug Fixes

The latest bug fixes on the master branch were published with 1.0:

  • Fix error messages translation
  • Fix ability to disable sort for a field
  • Fix translation warning on tab names
  • Fix Admin component crash while rendering if first resource isn't loaded yet (lutangar)
  • Fix missing menu dock for dashboard

Conclusion

We're incredibly happy of what admin-on-rest allows us to build in no time: gorgeous admin apps, that can run against any REST API (and even a GraphQL backend!). The end users are very happy, too!

The investment we made in admin-on-rest was definitely worth it: we can now deliver admin applications faster, without ever being blocked by the framework we use (which was one of the reasons we didn't use ng-admin very much lately). We receive RFPs from people all over the world asking us to help them build their admin in React. And we learned a lot in the process!

Thanks to the numerous contributors (49 to date), this project has reached a level of stability and performance that makes it worthy of the stable grade.

This encourages us to keep investing. The development of admin-on-rest will continue at a steady pace: we plan to keep the monthly release plan (but without BC break!). Follow this blog for future news about its development!