Data Fetching
You can build a react-admin app on top of any API, whether it uses REST, GraphQL, RPC, or even SOAP, regardless of the dialect it uses. This works because react-admin doesn’t use fetch
directly. Instead, it uses a Data Provider object to interface with your API and React Query to handle data fetching.
The Data Provider
In a react-admin app, you don’t write API calls using fetch
or axios
. Instead, you communicate with your API through an object called the dataProvider
.
The dataProvider
exposes a predefined interface that allows react-admin to query any API in a normalized way.
For instance, to query the API for a single record, react-admin calls dataProvider.getOne()
:
const response = await dataProvider.getOne('posts', { id: 123 });
console.log(response.data); // { id: 123, title: "hello, world" }
The Data Provider is responsible for transforming these method calls into HTTP requests and converting the responses into the format expected by react-admin. In technical terms, a Data Provider is an adapter for an API.
A Data Provider must implement the following methods:
const dataProvider = {
async getList(resource, { sort, filter, pagination }) => ({ data: Record[], total: number }),
async getOne(resource, { id }) => ({ data: Record }),
async getMany(resource, { ids }) => ({ data: Record[] }),
async getManyReference(resource, { target, id, sort, filter, pagination }) => ({ data: Record[], total: number }),
async create(resource, { data }) => ({ data: Record }),
async update(resource, { id, data }) => ({ data: Record }),
async updateMany(resource, { ids, data }) => ({ data: Identifier[] }),
async delete(resource, { id } ) => ({ data: Record }),
async deleteMany(resource, { ids }) => ({ data: Identifier[] }),
}
Tip: A Data Provider can have additional methods beyond these 9. For example, you can add custom methods for non-REST API endpoints, tree structure manipulations, or realtime updates.
The Data Provider is a key part of react-admin’s architecture. By standardizing the Data Provider interface, react-admin can offer powerful features, like reference handling, optimistic updates, and autogenerated CRUD components.
Backend Agnostic
Thanks to this adapter system, react-admin can communicate with any API. It doesn’t care if your API is a REST API, a GraphQL API, a SOAP API, a JSON-RPC API, or even a local API. It doesn’t care if your API is written in PHP, Python, Ruby, Java, or JavaScript. It doesn’t care if your API is a third-party API or a homegrown API.
React-admin ships with more than 50 data providers for popular API flavors.
You can also write your own Data Provider to fit your backend’s particularities. Data Providers can use fetch
, axios
, apollo-client
, or any other library to communicate with APIs. The Data Provider is also the ideal place to add custom HTTP headers, authentication, etc.
Check out the Data Provider Setup documentation for more details on how to set up a Data Provider in your app.
Calling The Data Provider
Many react-admin components use the Data Provider: page components like <List>
and <Edit>
, reference components like <ReferenceField>
and <ReferenceInput>
, action Buttons like <DeleteButton>
and <SaveButton>
, and many more.
If you need to call the Data Provider directly from your components, you can use the specialized hooks provided by react-admin:
useGetList
useGetOne
useGetMany
useGetManyReference
useCreate
useUpdate
useUpdateMany
useDelete
useDeleteMany
For instance, to call dataProvider.getOne()
, use the useGetOne
hook:
import { useGetOne } from 'react-admin';
import { Loading, Error } from './MyComponents';
const UserProfile = ({ userId }) => {
const { data: user, isPending, error } = useGetOne('users', { id: userId });
if (isPending) return <Loading />;
if (error) return <Error />;
if (!user) return null;
return (
<ul>
<li>Name: {user.name}</li>
<li>Email: {user.email}</li>
</ul>
)
};
You can also call the useDataProvider
hook to access the dataProvider
directly:
import { useDataProvider } from 'react-admin';
const BanUserButton = ({ userId }) => {
const dataProvider = useDataProvider();
const handleClick = () => {
dataProvider.update('users', { id: userId, data: { isBanned: true } });
};
return <Button label="Ban user" onClick={handleClick} />;
};
The Querying the API documentation lists all the hooks available for querying the API, as well as the options and return values for each.
React Query
React-admin uses TanStack Query to call the Data Provider. Specialized hooks like useGetOne
use TanStack Query’s hooks under the hood and accept the same options.
You can use any of TanStack Query’s hooks in your code:
useQuery
for reading datauseMutation
for writing data.
For instance, you can use useMutation
to call the dataProvider.update()
directly. This lets you track the mutation’s status and add side effects:
import { useDataProvider, useNotify } from 'react-admin';
import { useQuery } from '@tanstack/react-query';
const BanUserButton = ({ userId }) => {
const dataProvider = useDataProvider();
const notify = useNotify();
const { mutate, isPending } = useMutation({
mutationFn: () => dataProvider.update('users', { id: userId, data: { isBanned: true } }),
onSuccess: () => notify('User banned'),
});
return <Button label="Ban user" onClick={() => mutate()} disabled={isPending} />;
};
Check out the TanStack Query documentation for more information on how to use it.
Local API Mirror
React-admin caches query data locally in the browser and automatically reuses it to answer future queries whenever possible. By structuring and indexing the data by resource name and ID, React-admin offers several advantages:
- Stale-While-Revalidate: React-admin renders the UI immediately using cached data while fetching fresh data from the server in the background. Once the server response arrives, the UI seamlessly updates with the latest data.
- Data Sharing Between Views: When navigating from a list view to a show view, React-admin reuses data from the list to render the show view instantly, eliminating the need to wait for the
dataProvider.getOne()
response. - Optimistic Updates: When a user deletes or updates a record, React-admin immediately updates the local cache to reflect the change, providing instant UI feedback. The server request follows, and if it fails, React-admin reverts the local data and notifies the user.
- Auto Refresh: React-admin invalidates dependent queries after a successful mutation. TanStack Query then refetches the necessary data, ensuring the UI remains up-to-date automatically.
For example, when a user deletes a book in a list, React-admin immediately removes it, making the row disappear. After the API confirms the deletion, React-admin invalidates the list’s cache, refreshes it, and another record appears at the end of the list.
The local API mirror significantly enhances both the user experience (with a snappy and responsive UI) and the developer experience (by abstracting caching, invalidation, and optimistic updates).
Mutation Mode
React-admin provides three approaches for handling updates and deletions:
- Undoable (default): React-admin updates the UI immediately and displays an undo button. During this time, it doesn’t send a request to the server. If the user clicks the undo button, React-admin restores the previous UI state and cancels the server request. If the user doesn’t click the undo button, it sends the request to the server after the delay.
- Optimistic: React-admin updates the UI immediately and sends the request to the server simultaneously. If the server request fails, the UI is reverted to its previous state to maintain consistency.
- Pessimistic: React-admin sends the request to the server first. After the server confirms success, the UI is updated. If the request fails, it displays an error message to inform the user.
For each mutation hook or component, you can specify the mutation mode:
const DeletePostButton = ({ record }) => {
const [deleteOne] = useDelete(
'posts',
{ id: record.id },
{ mutationMode: 'pessimistic' }
);
const handleClick = () => deleteOne();
return <Button label="Delete" onClick={handleClick} />;
};
For details, refer to the Querying the API chapter.
Custom Data Provider Methods
Your API backend may expose non-CRUD endpoints, e.g., for calling Remote Procedure Calls (RPC).
For instance, let’s say your API exposes an endpoint to ban a user based on its id
:
POST /api/user/123/ban
The react-admin way to expose these endpoints to the app components is to add a custom method in the dataProvider
:
import simpleRestDataProvider from 'ra-data-simple-rest';
const baseDataProvider = simpleRestDataProvider('http://path.to.my.api/');
export const dataProvider = {
...baseDataProvider,
banUser: (userId: string) => {
return fetch(`/api/user/${userId}/ban`, { method: 'POST' })
.then(response => response.json());
},
}
export interface MyDataProvider extends DataProvider {
banUser: (userId: string) => Promise<Record<string, any>>;
}
Then you can use react-query’s useMutation
hook to call the dataProvider.banUser()
method:
import { useDataProvider } from 'react-admin';
import { useMutation } from '@tanstack/react-query';
import type { MyDataProvider } from './dataProvider';
const BanUserButton = ({ userId }: { userId: string }) => {
const dataProvider = useDataProvider<MyDataProvider>();
const { mutate, isPending } = useMutation({
mutationFn: () => dataProvider.banUser(userId)
});
return <Button label="Ban" onClick={() => mutate()} disabled={isPending} />;
};
Check the Calling Custom Methods documentation for more details.
Authentication
The dataProvider
often needs to send an authentication token in API requests. The authProvider
manages the authentication process. Here’s how the two work together:
- The user logs in with their email and password
- React-admin calls
authProvider.login()
with these credentials. - The
authProvider
sends the login request to the authentication backend. - The backend validates the credentials and returns an authentication token.
- The
authProvider
stores the token inlocalStorage
- When making requests, the
dataProvider
reads the token fromlocalStorage
and adds it to the request headers.
You must implement the interaction between the authProvider
and dataProvider
. Here’s an example for the auth provider:
// in authProvider.js
const authProvider = {
async login({ username, password }) {
const request = new Request('https://mydomain.com/authenticate', {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
let response;
try {
response = await fetch(request);
} catch (_error) {
throw new Error('Network error');
}
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
const { token } = await response.json();
localStorage.setItem('token', token);
},
async logout() {
localStorage.removeItem('token');
},
// ...
};
Many Data Providers, like simpleRestProvider
, support authentication. Here’s how you can configure it to include the token:
// in dataProvider.js
import { fetchUtils } from 'react-admin';
import simpleRestProvider from 'ra-data-simple-rest';
const fetchJson = (url, options = {}) => {
options.user = {
authenticated: true,
token: localStorage.getItem('token') // Include the token
};
return fetchUtils.fetchJson(url, options);
};
const dataProvider = simpleRestProvider('http://path.to.my.api/', fetchJson);
Check your Data Provider’s documentation for specific configuration options.
Relationships
React-admin simplifies working with relational APIs by managing related records at the component level. You can leverage relationship support without modifying your Data Provider or API.
For instance, let’s imagine an API exposing CRUD endpoints for books and authors:
┌──────────────┐ ┌────────────────┐
│ books │ │ authors │
│--------------│ │----------------│
│ id │ ┌───│ id │
│ author_id │╾──┘ │ first_name │
│ title │ │ last_name │
│ published_at │ │ date_of_birth │
└──────────────┘ └────────────────┘
The Book show page should display a book title and the name of its author. In a server-side framework, you would issue a SQL query with a JOIN clause. In React-admin, components request only the data they need, and React-admin handles the relationship resolution.
const BookShow = () => (
<Show>
<SimpleShowLayout>
<TextField source="id" />
<TextField source="title" />
<ReferenceField source="author_id" reference="authors" />
<TextField source="year" />
</SimpleShowLayout>
</Show>
);
In the example above, two components call the Data Provider on mount:
- The
Show
component callsdataProvider.getOne('books')
and receives a book with anauthor_id
field - The
ReferenceField
component reads the current book record and callsdataProvider.getOne('authors')
using theauthor_id
value
This approach improves the developer experience as you don’t need to build complex queries for each page. Components remain independent of each other and are easy to compose.
However, this cascade of Data Provider requests can appear inefficient regarding user-perceived performance. React-admin includes several optimizations to mitigate this:
- Local API Mirror (see above)
- Partial Rendering: React-admin first renders the page with the book data and updates it when the author data arrives. This ensures users see data as soon as possible.
- Query Aggregation: React-admin intercepts all calls to
dataProvider.getOne()
for related data when a<ReferenceField>
is used in a list. It aggregates and deduplicates the requested ids and issues a singledataProvider.getMany()
request. This technique effectively addresses the n+1 query problem, reduces server queries, and accelerates list view rendering. - Smart Loading Indicators:
<ReferenceField>
renders blank placeholders during the first second to avoid layout shifts when the response arrives. If the response takes longer, React-admin shows a spinner to indicate progress while maintaining a smooth user experience. - Embedded Data and Prefetching: Data providers can return data from related resources in the same response as the requested resource. React-admin uses this feature to avoid additional network requests and to display related data immediately.
Even on complex pages that aggregate data from multiple resources, Reference components optimize API requests, reducing their number while ensuring users quickly see the data they need.
Relationship support in React-admin works out of the box with any API that provides foreign keys. No special configuration is required for your API or Data Provider.
Here is a list of react-admin’s relationship components:
<ReferenceField>
<ReferenceArrayField>
<ReferenceManyField>
<ReferenceManyCount>
<ReferenceManyToManyField>
<ReferenceOneField>
<ReferenceInput>
<ReferenceArrayInput>
<ReferenceManyInput>
<ReferenceManyToManyInput>
<ReferenceOneInput>
To learn more about relationships, check out this tutorial: Handling Relationships in React Admin.
If a relationship component doesn’t fit your specific use case, you can always use a custom data provider method to fetch the required data.
Realtime
React-admin offers powerful realtime features to help you build collaborative applications based on the Publish / Subscribe (PubSub) pattern. The Realtime documentation explains how to use them.
These features are part of the Enterprise Edition.
Realtime Data Provider
The realtime features are backend agnostic. Just like CRUD operations, realtime operations rely on the data provider, using additional methods:
dataProvider.subscribe(topic, callback)
dataProvider.unsubscribe(topic, callback)
dataProvider.publish(topic, event)
(optional - publication is often done server-side)
In addition, to support the lock features, the dataProvider
must implement four more methods:
dataProvider.lock(resource, { id, identity, meta })
dataProvider.unlock(resource, { id, identity, meta })
dataProvider.getLock(resource, { id, meta })
dataProvider.getLocks(resource, { meta })
You can implement these features using any realtime backend, including:
- Mercure,
- API Platform,
- supabase,
- Socket.IO,
- Ably,
- and many more.
Check the Realtime Data Provider documentation for more information and for helpers to build your own realtime data provider.
Realtime Hooks And Components
Once your data provider has enabled realtime features, you can use these hooks and components to build realtime applications:
usePublish
useSubscribe
useSubscribeCallback
useSubscribeToRecord
useSubscribeToRecordList
useLock
useUnlock
useGetLock
useGetLockLive
useGetLocks
useGetLocksLive
useLockOnMount
useLockOnCall
useGetListLive
useGetOneLive
<ListLive>
<EditLive>
<ShowLive>
<MenuLive>
Refer to the Realtime documentation for more information.