Skip to content

Mastering React-admin Resources to Improve Your App Performance

January 18, 2022Morgane Riclet8 min read

React-admin logo

Let me tell you the story of how I improved my react-admin app runtime loading performance from 1 minute to 1 second.

I work at Theodo on a project with many websites. To configure and manage all of them, we had an administration tool (let's call it AdminV1). But AdminV1 was old: it was an Angular project coded with coffee.script and jade, and was not very maintainable. And the product was difficult to use: there were too many menus, UX was bad, it was difficult to onboard new administrators on the application.

The team decided to create a brand new application with modern technologies: AdminV2, with react-admin, a framework for building administration applications.

Everybody was happy: for developers, it was easier to implement new features. With a few lines of code, they could create tables to administrate users, app configurations and privileges. For Product Owners and administrators, the design was more ergonomic and it was easier to manage their applications.

We migrated many features from AdminV1 to AdminV2 and added new administration tools. Sponsors were delighted. But sprint after sprint, AdminV2 became slower to start. After some months, users had to wait more than one minute to access the app. It was painful to develop new features on AdminV2 because each hot reload took 1 minute, and users were frustrated by waiting so long to access the application.

We started to investigate and found that by deleting some Resources in our application, the speed at runtime increased significantly

But what are react-admin Resources ?

"<Resource> components are fundamental building blocks in react-admin apps. They are strings that refer to an entity type.", according to react-admin documentation.

To illustrate, on my project, we need to administrate users of websites. We have to create a resource "/user", associated with components:

  • UserList (for table view)
  • UserEdit (to edit an user)
  • UserCreate (to create a new user).
import UsersList from "components/Users/list";
import UsersEdit from "components/Users/edit";
import UsersCreate from "components/Users/create";

const UserResource = () => (
  <Resource
    key="/user"
    name="/user" // endpoint of the API to call to get users
    create={UsersCreate}
    list={UsersList}
    edit={UsersEdit}
  />
);

React-admin is in charge of storing this information. When the user goes on /user URL, react-admin displays the list, edit or create view and manages all the logic to get, edit and create users.

Sounds like magic, not helping us understand why creating many Resources degrades the performance.

I dived deeper into the react-admin code to understand how the framework creates resources and links them with components.

A <Resource /> component is declared like this:

const Resource = (props: ResourceProps) => {
  const { intent = "route", ...rest } = props;
  return intent === "registration" ? (
    <ResourceRegister {...rest} />
  ) : (
    <ResourceRoutes {...rest} />
  );
};

React-admin Code

Your intent can be either registration or route:

  • ResourceRegister's purpose is to dispatch an action that saves your Resource into a redux store. Each time you define a <Resource /> component in your app, react-admin will register your Resource's props into the redux store:

    • the name, which is the URL of the resource and also the API endpoint to fetch your entity data
    • which views are available to manage the entity. For our user entity, available views are list, edit and create
dispatch(
  registerResource({
    name,
    hasList: true,
    hasEdit: true,
    hasShow: false,
    hasCreate: true,
  })
);

React-admin Code

  • ResourceRoutes is rendered when a user wants to access the URL associated with the Resource. The URL (which is also generally the endpoint of the API) is stored in the Resource's name prop. ResourceRoutes renders all <Route /> components associated with each view defined in the Resource. For our Entity "user", it will be:
const ResourceRoutes = () => (
  <ResourceContextProvider value={name}>
    <Switch>
      <Route
        path={`${basePath}/create`} // Create view
        render={(routeProps) => (
          <WithPermissions component={CreateUser} {...routeProps} />
        )}
      />
      <Route
        path={`${basePath}/:id`} // Edit view
        render={(routeProps) => (
          <WithPermissions component={EditUser} {...routeProps} />
        )}
      />
      <Route
        path={`${basePath}`} // ListView
        render={(routeProps) => (
          <WithPermissions component={ListUser} {...routeProps} />
        )}
      />
    </Switch>
  </ResourceContextProvider>
);

React-admin Code

For example, if an administrator connects to a react-admin app and wants to see a list of users, the framework will load resources like this:

React-admin Resources schema

This allows calling the API only when the user goes on the associated Menu and to not call all the endpoints available in the application at runtime.

Ok, so what's going on with AdminV2 ?

In AdminV2, we defined our Resources like this:

const RootComponent = () =>
{
  const resources = getResources()
  return (
    <AdminContext {...props}>
      <AdminUI>
        {resources}
      </AdminUI>
    <AdminContext />
}

GetResources returned all Resources in our application: user Resources and other 600 Resources (yes that's a lot).

So what's happening when a user access AdminV2 ?

  1. First, over 600 <Resources /> components are rendered
  2. Then, each of these <Resources /> dispatches an action to store their data in redux store (the "registration" intent seen above)
  3. React-admin redux reducer gets these 600 actions and for each of them, it stores the data in the redux store
  4. Finally, when the reducer has updated the store with all resources action payload, the home page of AdminV2 is displayed

Old AdminV2 Resources rendering

These 4 steps take more than a minute. Eventually, the user has gone for a coffee, otherwise, he left the app and will never come back again

How to avoid this 1 minute of loading time?

We thought about diverse solutions:

1 - Load only resources to which the user has access

On AdminV2, to access each menu, the user needs to have the associated privilege. If the user has access to one menu, we don't have to load all resources, but only the resources used in the menu.

This solves the problem for regular users with few privileges. But Developers and Product Owners have access to all menus.

2 - Patch react-admin to modify Resource component

We thought of modifying directly the react-admin Resource component, to modify the dispatch action. Instead of dispatching one action for each Resource, we could dispatch one action for multiple resources. This way, we don't have to update the state 600 times.

But this solution is not very maintainable: it implies updating the patch each time we update react-admin.

3 - Lazy load Resources

We don't have to create every Resources at the initialization of the application. An administrator will never access all AdminV2 menus and, even if he wanted to, in most cases, he doesn't have all menus accesses. We need to load Resources only when the user needs them. So we decided to create them when the user accesses the associated url. We modified our RootComponent like this:

const RootComponent = () => {
  const [resources, setResources] = useState([]);
  const location = useLocation();

  useEffect(() => {
    const resourcesToAdd = getResources(location.pathname)
    setResources([...resources, ...resourcesToAdd]);
  }, [location.pathname])

  return (
    <AdminContext {...props}>
      <AdminUI>
        {resources}
      </AdminUI>
    <AdminContext />
  )
}

We modified the getResource function so that it takes the current URL in parameters and returned the Resources associated with the URL.

For example, if an administrator wants to access the user administration menu (URL '/user'), getResources will only return

<Resource name="/user" list={UserList} edit={UserEdit} create={UserCreate} />.

The RootComponent will render the new Resource:

  • The first intent will be registration and the Resource will be stored in redux state
  • The second intent will be route. React-admin will create the Routes for user entities and the administrator will access the user management menu

In order not to save resources each time the user goes back on the same URL, we added a filter:

const RootComponent = () => {
  const [resources, setResources] = useState([]);
  const [loadedResources, setLoadedResources] = useRef([]);
  const location = useLocation();

  useEffect(() => {
    const resourcesToAdd = getResources(location.pathname).filter(
      (resource) => !loadedResources.includes(resource.name)
    );
    setResources([...resources, ...resourcesToAdd]);
    setLoadedResources([
      ...loadedResources,
      ...resourcesToAdd.map((resource) => resource.name),
    ]);
  }, [location.pathname]);

  return <AdminUI>{resources}</AdminUI>;
};

Let's sum up

Before, the user had to wait more than one minute to access the app. Then the navigation was quite quick.

Old process to access User Menu on Admin V2

Now, the user access AdminV2 in one second. And the navigation stays quite quick!

New process to access User Menu on Admin V2

As developers, when using "black box" framework like react-admin, we do not always try to understand how it really works. By taking the time to explore the source code of these frameworks, by understanding what libraries they use, how they are implemented, we can save our time later and help us detect this kind of performance issue sooner.

That is the conclusion of AdminV2 performance story. And developers developed happily ever after and added a lot of features to AdminV2!

Morgane Riclet

Morgane Riclet

Web Developer at Theodo