# React-Admin React-admin is a frontend framework for building single-page applications running in the browser on top of REST/GraphQL APIs. Built with TypeScript, React, react-router, react-hook-form, react-query, and Material Design, it provides all the building blocks needed for data-driven admin applications including authentication, routing, forms, validation, datagrids, search, filtering, relationships, i18n, notifications, menus, theming, and caching. The framework follows an adapter approach using Data Providers and Auth Providers to connect to any API backend. React-admin is designed as a library of loosely coupled React components and hooks, allowing developers to replace any part with custom implementations. It supports optimistic rendering, undo functionality, and is fully customizable while maintaining excellent developer and user experiences out of the box. ## Admin Component The `` component is the root component of a react-admin app. It configures the application adapters (dataProvider, authProvider, i18nProvider), routes, and UI, creating context providers for all children components. ```jsx import { Admin, Resource, CustomRoutes } from 'react-admin'; import { Route } from 'react-router-dom'; import simpleRestProvider from 'ra-data-simple-rest'; import { dataProvider, authProvider, i18nProvider } from './providers'; import { Layout } from './layout'; import { Dashboard } from './dashboard'; import { PostList, PostEdit, PostCreate } from './posts'; import { UserList } from './users'; import { Settings } from './settings'; const App = () => ( } /> ); export default App; ``` ## Resource Component The `` component defines CRUD routes for a given resource, creates a ResourceContext for descendants, and stores resource definitions (name, icon, label) in a shared context. ```jsx import { Admin, Resource } from 'react-admin'; import jsonServerProvider from 'ra-data-json-server'; import PostIcon from '@mui/icons-material/Book'; import UserIcon from '@mui/icons-material/People'; import { PostList, PostCreate, PostEdit, PostShow } from './posts'; import { UserList, UserEdit } from './users'; import { CommentList } from './comments'; const App = () => ( {/* Full CRUD with custom icon */} record.title} /> {/* Read and edit only */} {/* Read-only resource */} ); ``` ## Data Provider The Data Provider is the interface between react-admin and your API. It handles all data fetching and mutations through a standardized set of methods. ```jsx // Custom data provider implementation const dataProvider = { getList: async (resource, params) => { const { page, perPage } = params.pagination; const { field, order } = params.sort; const query = { sort: JSON.stringify([field, order]), range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]), filter: JSON.stringify(params.filter), }; const url = `${API_URL}/${resource}?${new URLSearchParams(query)}`; const response = await fetch(url); const data = await response.json(); return { data, total: parseInt(response.headers.get('content-range').split('/').pop(), 10), }; }, getOne: async (resource, params) => { const response = await fetch(`${API_URL}/${resource}/${params.id}`); const data = await response.json(); return { data }; }, create: async (resource, params) => { const response = await fetch(`${API_URL}/${resource}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params.data), }); const data = await response.json(); return { data }; }, update: async (resource, params) => { const response = await fetch(`${API_URL}/${resource}/${params.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params.data), }); const data = await response.json(); return { data }; }, delete: async (resource, params) => { await fetch(`${API_URL}/${resource}/${params.id}`, { method: 'DELETE' }); return { data: params.previousData }; }, getMany: async (resource, params) => { const query = { filter: JSON.stringify({ id: params.ids }) }; const url = `${API_URL}/${resource}?${new URLSearchParams(query)}`; const response = await fetch(url); const data = await response.json(); return { data }; }, getManyReference: async (resource, params) => { const { page, perPage } = params.pagination; const { field, order } = params.sort; const query = { sort: JSON.stringify([field, order]), range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]), filter: JSON.stringify({ ...params.filter, [params.target]: params.id }), }; const url = `${API_URL}/${resource}?${new URLSearchParams(query)}`; const response = await fetch(url); const data = await response.json(); return { data, total: parseInt(response.headers.get('content-range').split('/').pop(), 10), }; }, updateMany: async (resource, params) => { const responses = await Promise.all( params.ids.map(id => fetch(`${API_URL}/${resource}/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params.data), }) ) ); return { data: params.ids }; }, deleteMany: async (resource, params) => { await Promise.all( params.ids.map(id => fetch(`${API_URL}/${resource}/${id}`, { method: 'DELETE' }) ) ); return { data: params.ids }; }, }; ``` ## useGetList Hook The `useGetList` hook calls `dataProvider.getList()` when the component mounts. It supports filtering, sorting, and pagination for fetching lists of records. ```jsx import { useGetList, useList, ListContextProvider, DataTable, DateField, Pagination } from 'react-admin'; const LatestNews = () => { const { data, total, isPending, error, refetch } = useGetList( 'posts', { pagination: { page: 1, perPage: 10 }, sort: { field: 'published_at', order: 'DESC' }, filter: { status: 'published' } } ); if (isPending) return
Loading...
; if (error) return
Error: {error.message}
; return (

Latest News ({total} articles)

    {data.map(record => (
  • {record.title}
  • ))}
); }; // Using with DataTable component const PostsWithDataTable = () => { const { data, isPending, error } = useGetList('posts', { pagination: { page: 1, perPage: 100 } }); const listContext = useList({ data, isPending, perPage: 10, sort: { field: 'published_at', order: 'DESC' } }); if (error) return

ERROR

; return ( ); }; ``` ## useCreate Hook The `useCreate` hook allows calling `dataProvider.create()` when the callback is executed. It supports optimistic, pessimistic, and undoable mutation modes. ```jsx import { useCreate, useNotify, useRedirect, useRecordContext } from 'react-admin'; // Basic usage const LikeButton = () => { const record = useRecordContext(); const [create, { isPending, error }] = useCreate(); const handleClick = () => { create('likes', { data: { postId: record.id } }); }; if (error) return

ERROR

; return ; }; // With success/error callbacks const CreateCommentButton = () => { const notify = useNotify(); const redirect = useRedirect(); const record = useRecordContext(); const [create, { isPending }] = useCreate( 'comments', { data: { postId: record.id, body: 'Great post!' } }, { mutationMode: 'optimistic', onSuccess: (data) => { notify('Comment created successfully'); redirect(`/posts/${record.id}/show`); }, onError: (error) => { notify(`Error: ${error.message}`, { type: 'error' }); }, } ); return ( ); }; ``` ## List Component The `` component is the root component for list pages. It fetches records from the data provider, creates a ListContext, and renders the page layout with title, buttons, filters, and pagination. ```jsx import { List, DataTable, DateField, BooleanField, SearchInput, TextInput, SelectInput, FilterButton, CreateButton, ExportButton, TopToolbar, useListContext } from 'react-admin'; // Basic list const PostList = () => ( ); // List with filters and custom actions const postFilters = [ , , , ]; const ListActions = () => { const { total, isPending } = useListContext(); return ( ); }; const AdvancedPostList = () => ( } sort={{ field: 'published_at', order: 'DESC' }} perPage={25} > ); ``` ## Create Component The `` component is the main component for creation pages. It prepares a form submit handler and renders the page title and actions, delegating form rendering to its children. ```jsx import { Create, SimpleForm, TextInput, DateInput, ReferenceInput, SelectInput, required, useNotify, useRedirect } from 'react-admin'; import { RichTextInput } from 'ra-input-rich-text'; // Basic create form const PostCreate = () => ( ); // Create with custom success handling const PostCreateWithRedirect = () => { const notify = useNotify(); const redirect = useRedirect(); const onSuccess = (data) => { notify('Post created successfully'); redirect(`/posts/${data.id}/show`); }; return ( ); }; ``` ## Edit Component The `` component is the main component for edition pages. It fetches a record based on the URL, prepares a form submit handler with update functionality, and supports undoable mutations by default. ```jsx import { Edit, SimpleForm, TextInput, DateInput, ReferenceManyField, DataTable, DateField, EditButton, required, useRecordContext } from 'react-admin'; import { RichTextInput } from 'ra-input-rich-text'; // Basic edit form const PostEdit = () => ( ); // Edit with related records const PostEditWithComments = () => ( ); // Edit with custom title const PostTitle = () => { const record = useRecordContext(); return Edit Post: {record ? `"${record.title}"` : ''}; }; const PostEditWithTitle = () => ( }> ); ``` ## SimpleForm Component The `` creates a form to edit a record, rendering its children (usually Input components) in a simple layout with one child per row. ```jsx import { Create, Edit, SimpleForm, TextInput, NumberInput, DateInput, BooleanInput, SaveButton, Toolbar, required, minLength, maxLength, email, useRedirect, useNotify } from 'react-admin'; // Form with validation const PostForm = () => ( ); // Form with custom toolbar const PostCreateToolbar = () => { const redirect = useRedirect(); const notify = useNotify(); return ( { notify('Post created'); redirect(false); } }} type="button" variant="text" /> ); }; const PostCreateWithToolbar = () => ( }> ); // Form with default values const postDefaultValue = () => ({ created_at: new Date(), status: 'draft', views: 0 }); const PostCreateWithDefaults = () => ( ); ``` ## Auth Provider The Auth Provider handles authentication and authorization logic. It exposes methods that react-admin calls when needed for login, logout, checking authentication status, and access control. ```jsx // Basic auth provider implementation const authProvider = { login: async ({ username, password }) => { const response = await fetch('https://api.example.com/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); if (!response.ok) { throw new Error('Invalid credentials'); } const { token, user } = await response.json(); localStorage.setItem('auth', JSON.stringify({ token, user })); }, logout: async () => { localStorage.removeItem('auth'); }, checkAuth: async () => { const auth = localStorage.getItem('auth'); if (!auth) { throw new Error('Not authenticated'); } }, checkError: async (error) => { if (error.status === 401 || error.status === 403) { localStorage.removeItem('auth'); throw new Error('Session expired'); } }, getIdentity: async () => { const auth = JSON.parse(localStorage.getItem('auth')); return { id: auth.user.id, fullName: auth.user.name, avatar: auth.user.avatar, }; }, getPermissions: async () => { const auth = JSON.parse(localStorage.getItem('auth')); return auth.user.role; }, canAccess: async ({ resource, action }) => { const auth = JSON.parse(localStorage.getItem('auth')); const role = auth.user.role; // Admin can do everything if (role === 'admin') return true; // Editors can edit but not delete if (role === 'editor' && action !== 'delete') return true; // Viewers can only read if (role === 'viewer' && action === 'list') return true; return false; }, }; // Using auth provider with data provider that adds auth headers import { fetchUtils } from 'react-admin'; import simpleRestProvider from 'ra-data-simple-rest'; const httpClient = (url, options = {}) => { if (!options.headers) { options.headers = new Headers({ Accept: 'application/json' }); } const auth = JSON.parse(localStorage.getItem('auth')); if (auth) { options.headers.set('Authorization', `Bearer ${auth.token}`); } return fetchUtils.fetchJson(url, options); }; const dataProvider = simpleRestProvider('https://api.example.com', httpClient); const App = () => ( ); ``` ## withLifecycleCallbacks The `withLifecycleCallbacks` helper allows adding lifecycle callbacks to the dataProvider for executing custom logic before or after data provider calls. ```jsx import { withLifecycleCallbacks } from 'react-admin'; import simpleRestProvider from 'ra-data-simple-rest'; const baseDataProvider = simpleRestProvider('https://api.example.com'); const dataProvider = withLifecycleCallbacks(baseDataProvider, [ { resource: 'posts', // Delete all comments before deleting a post beforeDelete: async (params, dataProvider) => { const { data: comments } = await dataProvider.getList('comments', { filter: { post_id: params.id }, pagination: { page: 1, perPage: 1000 }, sort: { field: 'id', order: 'DESC' }, }); await dataProvider.deleteMany('comments', { ids: comments.map(comment => comment.id) }); return params; }, // Convert uploaded images to base64 before update beforeUpdate: async (params, dataProvider) => { if (params.data.picture?.rawFile instanceof File) { const base64 = await convertFileToBase64(params.data.picture.rawFile); return { ...params, data: { ...params.data, picture: { src: base64, title: params.data.picture.title }, }, }; } return params; }, }, { resource: 'users', // Log user creation for audit afterCreate: async (params, dataProvider) => { await dataProvider.create('auditLogs', { data: { action: 'user_created', userId: params.data.id, timestamp: new Date().toISOString(), }, }); return params; }, }, ]); const convertFileToBase64 = (file) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(file); }); ``` ## Summary React-admin excels at building data-intensive admin applications with its declarative approach and comprehensive component library. The framework's primary use cases include building CRUD interfaces for REST/GraphQL APIs, creating custom dashboards with real-time data, implementing role-based access control, and developing multi-language admin panels. Its modular architecture allows developers to start with sensible defaults and progressively customize components as requirements evolve. Integration patterns typically involve connecting to existing APIs through custom data providers, implementing authentication via auth providers that work with OAuth, JWT, or session-based systems, and extending the UI with custom components built on Material UI. The framework's hooks-based approach (`useGetList`, `useCreate`, `useUpdate`, etc.) enables building custom views while leveraging react-admin's caching, optimistic updates, and error handling. For complex applications, developers can combine multiple data providers, add lifecycle callbacks for business logic, and use the enterprise edition features for advanced requirements like real-time updates, audit logs, and tree structures.