Try Live
Add Docs
Rankings
Pricing
Docs
Install
Theme
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
React-admin
https://github.com/marmelab/react-admin
Admin
A frontend Framework for building single-page applications running in the browser on top of
...
Tokens:
865,981
Snippets:
8,060
Trust Score:
9.5
Update:
4 days ago
Context
Skills
Chat
Benchmark
91.9
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# React-Admin React-admin is a frontend framework for building single-page admin applications that run in the browser on top of REST/GraphQL APIs. Built with TypeScript, React, and Material Design, it provides a comprehensive set of components for building admin interfaces including data tables, forms, filters, and CRUD operations. The framework uses an adapter pattern through "Data Providers" to communicate with any backend API, making it backend-agnostic and highly flexible. The core architecture centers around the `<Admin>` component which serves as the application root, managing routing, authentication, and data fetching. Resources are defined using `<Resource>` components that map API endpoints to CRUD pages. React-admin leverages React Query for data caching, react-hook-form for form management, and Material UI for the component library. The framework provides over 50 ready-to-use components including inputs, fields, buttons, and layouts, all of which can be customized or replaced with your own implementations. ## Admin Component The `<Admin>` component is the root component of a react-admin application. It configures the application adapters (dataProvider, authProvider, i18nProvider), routes, and UI. It creates context providers for all configuration and renders the main layout with routing. ```jsx import { Admin, Resource, ListGuesser, EditGuesser } from 'react-admin'; import simpleRestProvider from 'ra-data-simple-rest'; import { PostList, PostEdit, PostCreate } from './posts'; import { UserList } from './users'; import { Dashboard } from './Dashboard'; import { authProvider } from './authProvider'; import PostIcon from '@mui/icons-material/Book'; import UserIcon from '@mui/icons-material/Group'; const dataProvider = simpleRestProvider('https://api.example.com'); const App = () => ( <Admin dataProvider={dataProvider} authProvider={authProvider} dashboard={Dashboard} title="My Admin" > <Resource name="posts" list={PostList} edit={PostEdit} create={PostCreate} icon={PostIcon} /> <Resource name="users" list={UserList} icon={UserIcon} /> </Admin> ); export default App; ``` ## Resource Component The `<Resource>` component defines CRUD routes for a given API endpoint. It maps a resource name to React components for list, create, edit, and show views. Each resource automatically creates routes like `/posts`, `/posts/create`, `/posts/:id`, and `/posts/:id/show`. ```jsx import { Admin, Resource } from 'react-admin'; import { Route } from 'react-router-dom'; import { PostList, PostEdit, PostCreate, PostShow } from './posts'; import { CommentList, CommentEdit } from './comments'; const App = () => ( <Admin dataProvider={dataProvider}> {/* Full CRUD resource */} <Resource name="posts" list={PostList} edit={PostEdit} create={PostCreate} show={PostShow} recordRepresentation={(record) => record.title} /> {/* Read-only resource */} <Resource name="users" list={UserList} /> {/* Resource with nested routes */} <Resource name="authors" list={AuthorList} edit={AuthorDetail}> <Route path=":authorId/books" element={<BookList />} /> </Resource> {/* Resource with custom label */} <Resource name="v2/posts" options={{ label: 'Posts' }} list={PostList} /> </Admin> ); ``` ## Data Provider The Data Provider is the interface between react-admin and your API. It defines methods for CRUD operations that react-admin calls to fetch and save data. You can use existing providers or create a custom one for your API. ```jsx // Using a pre-built data provider import simpleRestProvider from 'ra-data-simple-rest'; import jsonServerProvider from 'ra-data-json-server'; import { fetchUtils } from 'react-admin'; // Simple REST provider const dataProvider = simpleRestProvider('https://api.example.com'); // With custom headers for authentication const httpClient = (url, options = {}) => { if (!options.headers) { options.headers = new Headers({ Accept: 'application/json' }); } const token = localStorage.getItem('token'); options.headers.set('Authorization', `Bearer ${token}`); return fetchUtils.fetchJson(url, options); }; const authenticatedDataProvider = simpleRestProvider('https://api.example.com', httpClient); // Custom data provider implementation const customDataProvider = { 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}?${stringify(query)}`; const { json, headers } = await fetchUtils.fetchJson(url); return { data: json, total: parseInt(headers.get('content-range').split('/').pop(), 10), }; }, getOne: async (resource, params) => { const { json } = await fetchUtils.fetchJson(`${API_URL}/${resource}/${params.id}`); return { data: json }; }, create: async (resource, params) => { const { json } = await fetchUtils.fetchJson(`${API_URL}/${resource}`, { method: 'POST', body: JSON.stringify(params.data), }); return { data: json }; }, update: async (resource, params) => { const { json } = await fetchUtils.fetchJson(`${API_URL}/${resource}/${params.id}`, { method: 'PUT', body: JSON.stringify(params.data), }); return { data: json }; }, delete: async (resource, params) => { const { json } = await fetchUtils.fetchJson(`${API_URL}/${resource}/${params.id}`, { method: 'DELETE', }); return { data: json }; }, getMany: async (resource, params) => { const query = { filter: JSON.stringify({ id: params.ids }) }; const url = `${API_URL}/${resource}?${stringify(query)}`; const { json } = await fetchUtils.fetchJson(url); return { data: json }; }, 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}?${stringify(query)}`; const { json, headers } = await fetchUtils.fetchJson(url); return { data: json, total: parseInt(headers.get('content-range').split('/').pop(), 10), }; }, updateMany: async (resource, params) => { const responses = await Promise.all( params.ids.map(id => fetchUtils.fetchJson(`${API_URL}/${resource}/${id}`, { method: 'PUT', body: JSON.stringify(params.data), }) ) ); return { data: responses.map(({ json }) => json.id) }; }, deleteMany: async (resource, params) => { const responses = await Promise.all( params.ids.map(id => fetchUtils.fetchJson(`${API_URL}/${resource}/${id}`, { method: 'DELETE', }) ) ); return { data: responses.map(({ json }) => json.id) }; }, }; ``` ## List Component The `<List>` component is the root component for list pages. It fetches a list of records from the data provider, provides context for sorting, filtering, and pagination, and renders the page layout with its children components. ```jsx import { List, DataTable, TextField, DateField, BooleanField, ReferenceField, EditButton, FilterButton, CreateButton, ExportButton, TopToolbar, TextInput, ReferenceInput, SelectInput, SearchInput, } from 'react-admin'; // Filter inputs for the list const postFilters = [ <SearchInput source="q" alwaysOn />, <TextInput label="Title" source="title" />, <ReferenceInput source="author_id" reference="authors"> <SelectInput optionText="name" /> </ReferenceInput>, ]; // Custom actions toolbar const ListActions = () => ( <TopToolbar> <FilterButton /> <CreateButton /> <ExportButton /> </TopToolbar> ); export const PostList = () => ( <List filters={postFilters} actions={<ListActions />} sort={{ field: 'published_at', order: 'DESC' }} perPage={25} > <DataTable rowClick="edit"> <DataTable.Col source="id" /> <DataTable.Col source="title" /> <DataTable.Col source="author_id"> <ReferenceField source="author_id" reference="authors" link="show"> <TextField source="name" /> </ReferenceField> </DataTable.Col> <DataTable.Col source="published_at" field={DateField} /> <DataTable.Col source="is_published" field={BooleanField} /> <DataTable.Col> <EditButton /> </DataTable.Col> </DataTable> </List> ); // Responsive list with SimpleList for mobile import { useMediaQuery } from '@mui/material'; import { SimpleList } from 'react-admin'; export const ResponsivePostList = () => { const isSmall = useMediaQuery(theme => theme.breakpoints.down('sm')); return ( <List> {isSmall ? ( <SimpleList primaryText={record => record.title} secondaryText={record => `${record.views} views`} tertiaryText={record => new Date(record.published_at).toLocaleDateString()} /> ) : ( <DataTable> <DataTable.Col source="id" /> <DataTable.Col source="title" /> <DataTable.Col source="published_at" field={DateField} /> </DataTable> )} </List> ); }; ``` ## Edit Component The `<Edit>` component is the main component for edition pages. It fetches a record by ID, prepares a form submit handler that calls `dataProvider.update()`, and renders the page title and actions. ```jsx import { Edit, SimpleForm, TextInput, DateInput, ReferenceInput, SelectInput, BooleanInput, required, useNotify, useRedirect, TopToolbar, ShowButton, ListButton, } from 'react-admin'; import RichTextInput from 'ra-input-rich-text'; // Custom toolbar actions const EditActions = () => ( <TopToolbar> <ShowButton /> <ListButton /> </TopToolbar> ); export const PostEdit = () => { const notify = useNotify(); const redirect = useRedirect(); const onSuccess = () => { notify('Post updated successfully'); redirect('list', 'posts'); }; return ( <Edit actions={<EditActions />} mutationMode="pessimistic" mutationOptions={{ onSuccess }} > <SimpleForm> <TextInput source="id" disabled /> <TextInput source="title" validate={required()} fullWidth /> <TextInput source="teaser" multiline rows={3} fullWidth /> <RichTextInput source="body" validate={required()} /> <ReferenceInput source="author_id" reference="authors"> <SelectInput optionText="name" validate={required()} /> </ReferenceInput> <DateInput source="published_at" /> <BooleanInput source="is_published" /> </SimpleForm> </Edit> ); }; // With optimistic updates and undo export const PostEditUndoable = () => ( <Edit mutationMode="undoable"> <SimpleForm> <TextInput source="title" validate={required()} /> <TextInput source="body" multiline /> </SimpleForm> </Edit> ); ``` ## Create Component The `<Create>` component is the main component for creation pages. It prepares a form submit handler that calls `dataProvider.create()` and renders the page with a form for entering new record data. ```jsx import { Create, SimpleForm, TextInput, DateInput, ReferenceInput, SelectInput, required, useNotify, useRedirect, } from 'react-admin'; import RichTextInput from 'ra-input-rich-text'; export const PostCreate = () => { const notify = useNotify(); const redirect = useRedirect(); const onSuccess = (data) => { notify('Post created successfully'); redirect('edit', 'posts', data.id); }; return ( <Create mutationOptions={{ onSuccess }}> <SimpleForm defaultValues={{ published_at: new Date() }}> <TextInput source="title" validate={required()} fullWidth /> <TextInput source="teaser" multiline rows={3} fullWidth /> <RichTextInput source="body" validate={required()} /> <ReferenceInput source="author_id" reference="authors"> <SelectInput optionText="name" validate={required()} /> </ReferenceInput> <DateInput source="published_at" /> </SimpleForm> </Create> ); }; // With data transformation before saving export const PostCreateWithTransform = () => ( <Create transform={(data) => ({ ...data, created_at: new Date().toISOString(), author_name: data.author_id ? undefined : 'Anonymous', })} > <SimpleForm> <TextInput source="title" validate={required()} /> <TextInput source="body" multiline /> </SimpleForm> </Create> ); ``` ## Auth Provider The Auth Provider handles authentication and authorization. It defines methods that react-admin calls for login, logout, checking authentication status, handling errors, and getting user identity and permissions. ```jsx // authProvider.js export const authProvider = { // Called when the user attempts to log in login: async ({ username, password }) => { const response = await fetch('https://api.example.com/login', { method: 'POST', body: JSON.stringify({ username, password }), headers: { 'Content-Type': 'application/json' }, }); if (!response.ok) { throw new Error('Invalid credentials'); } const { token, user } = await response.json(); localStorage.setItem('token', token); localStorage.setItem('user', JSON.stringify(user)); }, // Called when the user clicks on the logout button logout: async () => { localStorage.removeItem('token'); localStorage.removeItem('user'); }, // Called when the API returns an error checkError: async ({ status }) => { if (status === 401 || status === 403) { localStorage.removeItem('token'); throw new Error('Session expired'); } }, // Called when the user navigates to a new location checkAuth: async () => { if (!localStorage.getItem('token')) { throw new Error('Not authenticated'); } }, // Get the user's profile getIdentity: async () => { const user = JSON.parse(localStorage.getItem('user')); return { id: user.id, fullName: `${user.firstName} ${user.lastName}`, avatar: user.avatar, }; }, // Get user permissions (optional) getPermissions: async () => { const user = JSON.parse(localStorage.getItem('user')); return user.role; }, // Check access to specific resource/action (optional) canAccess: async ({ resource, action }) => { const user = JSON.parse(localStorage.getItem('user')); if (user.role === 'admin') return true; if (resource === 'users' && action === 'delete') return false; return true; }, }; // Usage in App.js import { Admin, Resource } from 'react-admin'; import { authProvider } from './authProvider'; const App = () => ( <Admin dataProvider={dataProvider} authProvider={authProvider} requireAuth > <Resource name="posts" list={PostList} /> </Admin> ); ``` ## Query Hooks React-admin provides hooks for querying the data provider. These hooks handle loading states, caching, and error handling using React Query under the hood. ```jsx import { useGetList, useGetOne, useGetMany, useGetManyReference, Loading, Error, } from 'react-admin'; // Fetch a list of records const LatestPosts = () => { const { data, total, isPending, error } = useGetList('posts', { pagination: { page: 1, perPage: 10 }, sort: { field: 'created_at', order: 'DESC' }, filter: { is_published: true }, }); if (isPending) return <Loading />; if (error) return <Error error={error} />; return ( <ul> {data.map(post => ( <li key={post.id}>{post.title}</li> ))} <li>Total: {total} posts</li> </ul> ); }; // Fetch a single record const UserProfile = ({ userId }) => { const { data: user, isPending, error } = useGetOne('users', { id: userId }); if (isPending) return <Loading />; if (error) return <Error error={error} />; return ( <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> </div> ); }; // Fetch multiple records by IDs const AuthorNames = ({ authorIds }) => { const { data: authors, isPending } = useGetMany('authors', { ids: authorIds }); if (isPending) return <Loading />; return ( <span>{authors.map(a => a.name).join(', ')}</span> ); }; // Fetch related records (one-to-many) const PostComments = ({ postId }) => { const { data: comments, isPending } = useGetManyReference('comments', { target: 'post_id', id: postId, pagination: { page: 1, perPage: 25 }, sort: { field: 'created_at', order: 'DESC' }, }); if (isPending) return <Loading />; return ( <ul> {comments.map(comment => ( <li key={comment.id}>{comment.body}</li> ))} </ul> ); }; // With query options const PostWithOptions = ({ id }) => { const { data, isPending } = useGetOne( 'posts', { id }, { enabled: id !== undefined, staleTime: 60000, // 1 minute onSuccess: (data) => console.log('Fetched:', data), onError: (error) => console.error('Error:', error), } ); if (isPending) return <Loading />; return <div>{data.title}</div>; }; ``` ## Mutation Hooks React-admin provides hooks for mutating data (create, update, delete). These hooks return a mutate function and track the mutation state. ```jsx import { useCreate, useUpdate, useDelete, useUpdateMany, useDeleteMany, useNotify, useRedirect, useRecordContext, Button, } from 'react-admin'; // Create a new record const CreatePostButton = () => { const [create, { isPending }] = useCreate(); const notify = useNotify(); const handleClick = () => { create( 'posts', { data: { title: 'New Post', body: 'Content here' } }, { onSuccess: (data) => { notify('Post created'); console.log('Created post:', data); }, onError: (error) => { notify(`Error: ${error.message}`, { type: 'error' }); }, } ); }; return <Button onClick={handleClick} disabled={isPending} label="Create Post" />; }; // Update a record const ApproveButton = () => { const record = useRecordContext(); const [update, { isPending }] = useUpdate(); const notify = useNotify(); const handleClick = () => { update( 'comments', { id: record.id, data: { is_approved: true }, previousData: record, }, { mutationMode: 'undoable', onSuccess: () => notify('Comment approved', { undoable: true }), } ); }; return ( <Button onClick={handleClick} disabled={isPending || record.is_approved} label="Approve" /> ); }; // Delete a record const DeletePostButton = () => { const record = useRecordContext(); const [deleteOne, { isPending }] = useDelete(); const notify = useNotify(); const redirect = useRedirect(); const handleClick = () => { deleteOne( 'posts', { id: record.id, previousData: record }, { mutationMode: 'pessimistic', onSuccess: () => { notify('Post deleted'); redirect('list', 'posts'); }, } ); }; return <Button onClick={handleClick} disabled={isPending} label="Delete" color="error" />; }; // Bulk update const PublishSelectedButton = ({ selectedIds }) => { const [updateMany, { isPending }] = useUpdateMany(); const notify = useNotify(); const handleClick = () => { updateMany( 'posts', { ids: selectedIds, data: { is_published: true } }, { onSuccess: () => notify('Posts published'), } ); }; return <Button onClick={handleClick} disabled={isPending} label="Publish Selected" />; }; // Bulk delete const DeleteSelectedButton = ({ selectedIds }) => { const [deleteMany, { isPending }] = useDeleteMany(); const notify = useNotify(); const handleClick = () => { deleteMany( 'posts', { ids: selectedIds }, { mutationMode: 'undoable', onSuccess: () => notify('Posts deleted', { undoable: true }), } ); }; return <Button onClick={handleClick} disabled={isPending} label="Delete Selected" color="error" />; }; ``` ## Input Components Input components are used in forms to edit record fields. They integrate with react-hook-form and support validation, formatting, and various data types. ```jsx import { SimpleForm, TextInput, NumberInput, BooleanInput, DateInput, DateTimeInput, SelectInput, AutocompleteInput, ReferenceInput, ArrayInput, SimpleFormIterator, ImageInput, ImageField, PasswordInput, required, minLength, maxLength, email, regex, choices, } from 'react-admin'; const PostForm = () => ( <SimpleForm> {/* Text input with validation */} <TextInput source="title" validate={[required(), minLength(5), maxLength(200)]} fullWidth /> {/* Multiline text input */} <TextInput source="body" multiline rows={5} fullWidth /> {/* Number input */} <NumberInput source="views" min={0} step={1} /> {/* Boolean toggle */} <BooleanInput source="is_published" /> {/* Date picker */} <DateInput source="published_at" /> {/* DateTime picker */} <DateTimeInput source="created_at" disabled /> {/* Select with static choices */} <SelectInput source="status" choices={[ { id: 'draft', name: 'Draft' }, { id: 'published', name: 'Published' }, { id: 'archived', name: 'Archived' }, ]} /> {/* Autocomplete with static choices */} <AutocompleteInput source="category" choices={[ { id: 'tech', name: 'Technology' }, { id: 'science', name: 'Science' }, { id: 'arts', name: 'Arts' }, ]} /> {/* Reference input (foreign key) */} <ReferenceInput source="author_id" reference="authors"> <AutocompleteInput optionText="name" /> </ReferenceInput> {/* Array of objects */} <ArrayInput source="tags"> <SimpleFormIterator> <TextInput source="name" /> <SelectInput source="type" choices={[ { id: 'topic', name: 'Topic' }, { id: 'audience', name: 'Audience' }, ]} /> </SimpleFormIterator> </ArrayInput> {/* Image upload */} <ImageInput source="cover_image" accept="image/*"> <ImageField source="src" title="title" /> </ImageInput> {/* Password input */} <PasswordInput source="password" validate={required()} /> {/* Email with validation */} <TextInput source="email" validate={[required(), email()]} /> {/* Custom regex validation */} <TextInput source="phone" validate={regex(/^\+?[0-9]{10,14}$/, 'Invalid phone number')} /> </SimpleForm> ); ``` ## Field Components Field components display record data in read-only mode. They are used in List and Show views to render different data types with appropriate formatting. ```jsx import { Show, SimpleShowLayout, TextField, NumberField, DateField, BooleanField, EmailField, UrlField, ImageField, RichTextField, ArrayField, SingleFieldList, ChipField, ReferenceField, ReferenceManyField, DataTable, FunctionField, useRecordContext, } from 'react-admin'; // Custom field component const FullNameField = () => { const record = useRecordContext(); if (!record) return null; return <span>{record.firstName} {record.lastName}</span>; }; export const PostShow = () => ( <Show> <SimpleShowLayout> {/* Plain text */} <TextField source="title" /> {/* Number with formatting */} <NumberField source="views" options={{ style: 'decimal' }} /> {/* Currency */} <NumberField source="price" options={{ style: 'currency', currency: 'USD' }} /> {/* Date with formatting */} <DateField source="published_at" showTime /> {/* Boolean as Yes/No */} <BooleanField source="is_published" /> {/* Clickable email */} <EmailField source="author_email" /> {/* Clickable URL */} <UrlField source="website" target="_blank" /> {/* Image */} <ImageField source="cover_image" title="Cover" /> {/* Rich text / HTML */} <RichTextField source="body" /> {/* Reference to another resource */} <ReferenceField source="author_id" reference="authors" link="show"> <TextField source="name" /> </ReferenceField> {/* Array of values as chips */} <ArrayField source="tags"> <SingleFieldList> <ChipField source="name" /> </SingleFieldList> </ArrayField> {/* Related records (one-to-many) */} <ReferenceManyField reference="comments" target="post_id" label="Comments"> <DataTable> <DataTable.Col source="body" /> <DataTable.Col source="created_at" field={DateField} /> </DataTable> </ReferenceManyField> {/* Custom computed field */} <FunctionField label="Summary" render={record => `${record.title} (${record.views} views)`} /> {/* Custom field component */} <FullNameField label="Author Name" /> </SimpleShowLayout> </Show> ); ``` ## Lifecycle Callbacks with withLifecycleCallbacks The `withLifecycleCallbacks` helper allows you to add hooks before or after data provider calls, useful for data transformation, cascade deletes, or file uploads. ```jsx import { withLifecycleCallbacks, DataProvider } from 'react-admin'; import simpleRestProvider from 'ra-data-simple-rest'; const baseDataProvider = simpleRestProvider('https://api.example.com'); const dataProvider = withLifecycleCallbacks(baseDataProvider, [ { resource: 'posts', // Delete related 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: 'ASC' }, }); await dataProvider.deleteMany('comments', { ids: comments.map(c => c.id) }); return params; }, // Convert image to base64 before saving beforeUpdate: async (params, dataProvider) => { if (params.data.cover_image?.rawFile) { const base64 = await convertFileToBase64(params.data.cover_image.rawFile); return { ...params, data: { ...params.data, cover_image: base64 }, }; } return params; }, // Add timestamp after creation afterCreate: async (result, dataProvider) => { console.log('Post created at:', new Date().toISOString()); return result; }, }, { resource: 'users', // Hash password before save beforeSave: async (params, dataProvider) => { if (params.data.password) { return { ...params, data: { ...params.data, password: await hashPassword(params.data.password), }, }; } return params; }, }, ]); // Helper function for file conversion const convertFileToBase64 = (file) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(file); }); ``` ## useRecordContext Hook The `useRecordContext` hook provides access to the current record in components rendered within a RecordContext, such as inside DataTable rows, Show pages, or Edit pages. ```jsx import { useRecordContext, Button, useUpdate, useNotify } from 'react-admin'; // Custom action button using record context const PublishButton = () => { const record = useRecordContext(); const [update, { isPending }] = useUpdate(); const notify = useNotify(); if (!record) return null; if (record.is_published) return <span>Published</span>; const handleClick = () => { update( 'posts', { id: record.id, data: { is_published: true }, previousData: record }, { onSuccess: () => notify('Published!') } ); }; return ( <Button onClick={handleClick} disabled={isPending} label="Publish" /> ); }; // Custom field with styling based on record const StatusBadge = () => { const record = useRecordContext(); if (!record) return null; const color = record.status === 'active' ? 'green' : record.status === 'pending' ? 'orange' : 'red'; return ( <span style={{ backgroundColor: color, color: 'white', padding: '2px 8px', borderRadius: '4px', }}> {record.status} </span> ); }; // Usage in DataTable import { List, DataTable, TextField } from 'react-admin'; const PostList = () => ( <List> <DataTable> <DataTable.Col source="title" /> <DataTable.Col label="Status"> <StatusBadge /> </DataTable.Col> <DataTable.Col> <PublishButton /> </DataTable.Col> </DataTable> </List> ); ``` ## Custom Routes React-admin allows you to add custom routes to your application using the `<CustomRoutes>` component. These routes can be rendered inside or outside the main layout. ```jsx import { Admin, Resource, CustomRoutes } from 'react-admin'; import { Route } from 'react-router-dom'; // Custom page components const Settings = () => ( <div> <h1>Settings</h1> <p>Configure your preferences here.</p> </div> ); const Dashboard = () => ( <div> <h1>Dashboard</h1> <p>Welcome to the admin dashboard!</p> </div> ); const PublicPage = () => ( <div> <h1>Public Page</h1> <p>This page is accessible without authentication.</p> </div> ); const App = () => ( <Admin dataProvider={dataProvider} authProvider={authProvider}> <Resource name="posts" list={PostList} /> {/* Routes inside the main layout (with sidebar, app bar) */} <CustomRoutes> <Route path="/settings" element={<Settings />} /> <Route path="/dashboard" element={<Dashboard />} /> </CustomRoutes> {/* Routes outside the layout (no sidebar, no app bar) */} <CustomRoutes noLayout> <Route path="/public" element={<PublicPage />} /> <Route path="/register" element={<RegisterPage />} /> </CustomRoutes> </Admin> ); ``` ## Summary React-admin is designed for building admin and B2B applications with complex CRUD requirements. Its main use cases include: content management systems (CMS), customer relationship management (CRM), e-commerce back-offices, internal tools, and dashboards. The framework excels at rapidly building feature-rich admin interfaces with minimal boilerplate code, while providing escape hatches for customization at every level. Integration patterns in react-admin follow a provider-based architecture. The Data Provider abstracts API communication, allowing the same UI code to work with REST APIs, GraphQL, Firebase, Supabase, or any other backend. The Auth Provider handles authentication flows from simple username/password to complex OAuth and SSO scenarios. For existing React applications, react-admin can be embedded as a sub-route using the `basename` prop, making it easy to add an admin section to any React app. Components are designed to be composable and replaceable, so you can start with react-admin's defaults and gradually customize individual pieces as needed.