A frontend Framework for building admin applications on top of REST services, using ES6, React and Material UI
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
React Admin provides powerful advanced features including data export functionality, application preferences system, persistent stores, caching strategies, and comprehensive utilities for building sophisticated admin applications.
React Admin provides built-in data export capabilities with customizable formats and processing.
import { Exporter } from 'react-admin';
type Exporter = (
data: any[],
fetchRelatedRecords: FetchRelatedRecords,
dataProvider: DataProvider,
resource?: string
) => void | Promise<void>;
type FetchRelatedRecords = (
data: any[],
field: string,
resource: string
) => Promise<any[]>;Hook for accessing export functionality in custom components.
import { useExporter } from 'react-admin';
const useExporter: () => {
exporter: Exporter;
loading: boolean;
error: any;
};Pre-built button for triggering data export.
import { ExportButton } from 'react-admin';
interface ExportButtonProps {
disabled?: boolean;
exporter?: Exporter;
icon?: React.ReactElement;
label?: string;
maxResults?: number;
resource?: string;
sort?: SortPayload;
filter?: any;
className?: string;
sx?: any;
}
const ExportButton: React.FC<ExportButtonProps>;import {
defaultExporter,
downloadCSV,
fetchRelatedRecords
} from 'react-admin';
const defaultExporter: Exporter;
const downloadCSV: (data: any[], filename?: string) => void;
const fetchRelatedRecords: FetchRelatedRecords;import {
List,
Datagrid,
TextField,
ExportButton,
TopToolbar,
downloadCSV,
fetchRelatedRecords,
useDataProvider
} from 'react-admin';
// Basic export with default CSV exporter
const PostListActions = () => (
<TopToolbar>
<ExportButton />
</TopToolbar>
);
// Custom exporter with related data
const customPostExporter = async (posts, fetchRelatedRecords, dataProvider) => {
// Fetch related categories and authors
const postsWithCategories = await fetchRelatedRecords(posts, 'categoryId', 'categories');
const postsWithAuthors = await fetchRelatedRecords(postsWithCategories, 'authorId', 'users');
// Transform data for export
const exportData = postsWithAuthors.map(post => ({
id: post.id,
title: post.title,
status: post.status,
category: post.category?.name || 'Unknown',
author: post.author ? `${post.author.firstName} ${post.author.lastName}` : 'Unknown',
createdAt: new Date(post.createdAt).toLocaleDateString(),
wordCount: post.content?.split(' ').length || 0
}));
downloadCSV(exportData, 'posts-detailed');
};
const PostList = () => (
<List actions={<PostListActions />} exporter={customPostExporter}>
<Datagrid>
<TextField source="title" />
<TextField source="status" />
<DateField source="createdAt" />
</Datagrid>
</List>
);
// Excel export example
import * as XLSX from 'xlsx';
const excelExporter = (data) => {
const worksheet = XLSX.utils.json_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Posts');
XLSX.writeFile(workbook, 'posts.xlsx');
};
// PDF export example
import jsPDF from 'jspdf';
import 'jspdf-autotable';
const pdfExporter = (data) => {
const doc = new jsPDF();
doc.text('Posts Report', 20, 10);
const tableData = data.map(post => [
post.id,
post.title,
post.status,
new Date(post.createdAt).toLocaleDateString()
]);
doc.autoTable({
head: [['ID', 'Title', 'Status', 'Created']],
body: tableData,
startY: 20
});
doc.save('posts.pdf');
};React Admin provides a preferences system for storing user interface customizations.
import { usePreference } from 'react-admin';
interface UsePreferenceResult<T = any> {
0: T;
1: (value: T) => void;
identity: T;
setValue: (value: T) => void;
}
const usePreference: <T = any>(
key: string,
defaultValue?: T
) => UsePreferenceResult<T>;import {
usePreferencesEditor,
PreferencesEditorContext,
PreferencesEditorContextProvider
} from 'react-admin';
interface PreferencesEditorContextValue {
isEnabled: boolean;
enable: () => void;
disable: () => void;
preferenceKey: string;
setPreferenceKey: (key: string) => void;
}
const usePreferencesEditor: () => PreferencesEditorContextValue;import { Configurable } from 'react-admin';
interface ConfigurableProps {
children: React.ReactNode;
editor?: React.ComponentType<ConfigurableEditorProps>;
preferenceKey?: string;
sx?: any;
}
const Configurable: React.FC<ConfigurableProps>;import {
usePreference,
Configurable,
List,
Datagrid,
TextField,
BooleanField
} from 'react-admin';
// Custom component with preferences
const CustomizableDashboard = () => {
const [showStats, setShowStats] = usePreference('dashboard.showStats', true);
const [refreshInterval, setRefreshInterval] = usePreference('dashboard.refreshInterval', 30000);
const [layout, setLayout] = usePreference('dashboard.layout', 'grid');
return (
<Configurable preferenceKey="dashboard">
<div>
<div>
<label>
<input
type="checkbox"
checked={showStats}
onChange={(e) => setShowStats(e.target.checked)}
/>
Show Statistics
</label>
</div>
<div>
<label>
Refresh Interval:
<select
value={refreshInterval}
onChange={(e) => setRefreshInterval(Number(e.target.value))}
>
<option value={15000}>15 seconds</option>
<option value={30000}>30 seconds</option>
<option value={60000}>1 minute</option>
</select>
</label>
</div>
{showStats && <StatsWidget refreshInterval={refreshInterval} />}
<div className={`layout-${layout}`}>
<MainContent />
</div>
</div>
</Configurable>
);
};
// Configurable field visibility
const ConfigurablePostList = () => {
const [showId, setShowId] = usePreference('postList.showId', false);
const [showDate, setShowDate] = usePreference('postList.showDate', true);
return (
<List>
<Datagrid>
{showId && <TextField source="id" />}
<TextField source="title" />
<TextField source="status" />
{showDate && <DateField source="createdAt" />}
</Datagrid>
</List>
);
};React Admin provides persistent storage for application state.
import { Store } from 'react-admin';
interface Store {
getItem: (key: string) => any;
setItem: (key: string, value: any) => void;
removeItem: (key: string) => void;
removeItems: (keys: string[]) => void;
reset: () => void;
}import { localStorageStore, memoryStore } from 'react-admin';
const localStorageStore: Store;
const memoryStore: Store;import {
useStore,
useStoreContext,
useRemoveFromStore,
useRemoveItemsFromStore,
useResetStore
} from 'react-admin';
const useStore: <T = any>(key: string, defaultValue?: T) => [T, (value: T) => void];
const useStoreContext: () => Store;
const useRemoveFromStore: () => (key: string) => void;
const useRemoveItemsFromStore: () => (keys: string[]) => void;
const useResetStore: () => () => void;import { Store } from 'react-admin';
// Custom store with encryption
class EncryptedLocalStorageStore implements Store {
private encrypt(value: any): string {
return btoa(JSON.stringify(value));
}
private decrypt(encrypted: string): any {
try {
return JSON.parse(atob(encrypted));
} catch {
return null;
}
}
getItem(key: string): any {
const encrypted = localStorage.getItem(key);
return encrypted ? this.decrypt(encrypted) : null;
}
setItem(key: string, value: any): void {
localStorage.setItem(key, this.encrypt(value));
}
removeItem(key: string): void {
localStorage.removeItem(key);
}
removeItems(keys: string[]): void {
keys.forEach(key => localStorage.removeItem(key));
}
reset(): void {
localStorage.clear();
}
}
const encryptedStore = new EncryptedLocalStorageStore();
// Usage in Admin
<Admin store={encryptedStore} dataProvider={dataProvider}>
<Resource name="posts" list={PostList} />
</Admin>import { useStore, useResetStore } from 'react-admin';
const UserPreferences = () => {
const [theme, setTheme] = useStore('user.theme', 'light');
const [sidebar, setSidebar] = useStore('user.sidebarCollapsed', false);
const [language, setLanguage] = useStore('user.language', 'en');
const resetStore = useResetStore();
return (
<div>
<h2>User Preferences</h2>
<div>
<label>
Theme:
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
</div>
<div>
<label>
<input
type="checkbox"
checked={sidebar}
onChange={(e) => setSidebar(e.target.checked)}
/>
Collapse Sidebar
</label>
</div>
<button onClick={resetStore}>
Reset All Preferences
</button>
</div>
);
};
// Persistent form draft
const DraftManager = () => {
const [draft, setDraft] = useStore('form.postDraft', {});
const saveDraft = (formData) => {
setDraft({
...formData,
savedAt: new Date().toISOString()
});
};
const clearDraft = () => {
setDraft({});
};
return { draft, saveDraft, clearDraft };
};import {
useCheckForApplicationUpdate,
CheckForApplicationUpdate,
ApplicationUpdatedNotification
} from 'react-admin';
const useCheckForApplicationUpdate: () => {
updateAvailable: boolean;
checkForUpdate: () => void;
};
const CheckForApplicationUpdate: React.FC<{
interval?: number;
url?: string;
disabled?: boolean;
}>;
const ApplicationUpdatedNotification: React.FC;import {
Layout,
CheckForApplicationUpdate,
ApplicationUpdatedNotification
} from 'react-admin';
const CustomLayout = ({ children, ...props }) => (
<>
<Layout {...props}>
{children}
</Layout>
<CheckForApplicationUpdate interval={60000} />
<ApplicationUpdatedNotification />
</>
);
// Manual update checking
const UpdateChecker = () => {
const { updateAvailable, checkForUpdate } = useCheckForApplicationUpdate();
return (
<div>
<button onClick={checkForUpdate}>
Check for Updates
</button>
{updateAvailable && (
<div style={{ color: 'orange' }}>
Update available! Please refresh the page.
</div>
)}
</div>
);
};import {
removeEmpty,
removeKey,
getMutationMode,
linkToRecord,
escapePath
} from 'react-admin';
const removeEmpty: (object: any) => any;
const removeKey: (object: any, key: string) => any;
const getMutationMode: () => 'pessimistic' | 'optimistic' | 'undoable';
const linkToRecord: (basePath: string, id: Identifier, type?: string) => string;
const escapePath: (path: string) => string;import { asyncDebounce } from 'react-admin';
const asyncDebounce: <T extends (...args: any[]) => Promise<any>>(
func: T,
delay: number
) => T;import { mergeRefs, shallowEqual } from 'react-admin';
const mergeRefs: <T = any>(...refs: React.Ref<T>[]) => React.RefCallback<T>;
const shallowEqual: (a: any, b: any) => boolean;import { useWhyDidYouUpdate, useEvent } from 'react-admin';
const useWhyDidYouUpdate: (name: string, props: Record<string, any>) => void;
const useEvent: <T extends (...args: any[]) => any>(handler: T) => T;import {
useDataProvider,
useStore,
useNotify,
asyncDebounce
} from 'react-admin';
const useAdvancedDataManager = () => {
const dataProvider = useDataProvider();
const [cache, setCache] = useStore('dataCache', {});
const notify = useNotify();
// Debounced search function
const debouncedSearch = asyncDebounce(async (query: string) => {
try {
const { data } = await dataProvider.getList('posts', {
pagination: { page: 1, perPage: 10 },
sort: { field: 'score', order: 'DESC' },
filter: { q: query }
});
return data;
} catch (error) {
notify('Search failed', { type: 'error' });
return [];
}
}, 300);
// Cached data fetcher
const getCachedData = async (resource: string, id: string) => {
const cacheKey = `${resource}:${id}`;
if (cache[cacheKey]) {
return cache[cacheKey];
}
try {
const { data } = await dataProvider.getOne(resource, { id });
setCache({ ...cache, [cacheKey]: data });
return data;
} catch (error) {
notify(`Failed to fetch ${resource}`, { type: 'error' });
return null;
}
};
return {
search: debouncedSearch,
getCachedData,
clearCache: () => setCache({})
};
};import { useStore, useDataProvider } from 'react-admin';
const useAnalytics = () => {
const [events, setEvents] = useStore('analytics.events', []);
const dataProvider = useDataProvider();
const trackEvent = (eventType: string, data: any) => {
const event = {
type: eventType,
data,
timestamp: new Date().toISOString(),
userId: getCurrentUserId()
};
setEvents([...events, event]);
// Send to analytics service
sendToAnalytics(event);
};
const trackPageView = (resource: string, action: string) => {
trackEvent('page_view', { resource, action });
};
const trackUserAction = (action: string, resource: string, recordId?: string) => {
trackEvent('user_action', { action, resource, recordId });
};
return {
trackEvent,
trackPageView,
trackUserAction,
events
};
};
// Usage in components
const AnalyticsWrapper = ({ children, resource, action }) => {
const { trackPageView } = useAnalytics();
useEffect(() => {
trackPageView(resource, action);
}, [resource, action]);
return children;
};import { useWhyDidYouUpdate, useEvent } from 'react-admin';
const usePerformanceMonitor = (componentName: string, props: any) => {
const [renderCount, setRenderCount] = useState(0);
const [renderTimes, setRenderTimes] = useState<number[]>([]);
// Track renders in development
if (process.env.NODE_ENV === 'development') {
useWhyDidYouUpdate(componentName, props);
}
useEffect(() => {
const startTime = performance.now();
return () => {
const endTime = performance.now();
const renderTime = endTime - startTime;
setRenderCount(prev => prev + 1);
setRenderTimes(prev => [...prev.slice(-19), renderTime]); // Keep last 20 renders
};
});
const avgRenderTime = renderTimes.length > 0
? renderTimes.reduce((sum, time) => sum + time, 0) / renderTimes.length
: 0;
return {
renderCount,
avgRenderTime,
lastRenderTime: renderTimes[renderTimes.length - 1] || 0
};
};
// Performance dashboard component
const PerformanceDashboard = () => {
const [metrics, setMetrics] = useStore('performance.metrics', {});
return (
<div>
<h2>Performance Metrics</h2>
{Object.entries(metrics).map(([component, data]) => (
<div key={component}>
<h3>{component}</h3>
<p>Renders: {data.renderCount}</p>
<p>Avg Time: {data.avgRenderTime.toFixed(2)}ms</p>
</div>
))}
</div>
);
};import { useStore } from 'react-admin';
const useFeatureFlags = () => {
const [flags, setFlags] = useStore('featureFlags', {});
const isEnabled = (feature: string): boolean => {
return flags[feature] === true;
};
const enableFeature = (feature: string) => {
setFlags({ ...flags, [feature]: true });
};
const disableFeature = (feature: string) => {
setFlags({ ...flags, [feature]: false });
};
const toggleFeature = (feature: string) => {
setFlags({ ...flags, [feature]: !flags[feature] });
};
return {
flags,
isEnabled,
enableFeature,
disableFeature,
toggleFeature
};
};
// Feature flag wrapper component
const FeatureGuard = ({ feature, children, fallback = null }) => {
const { isEnabled } = useFeatureFlags();
return isEnabled(feature) ? children : fallback;
};
// Usage
const AdminPanel = () => (
<div>
<FeatureGuard feature="advancedAnalytics">
<AdvancedAnalyticsWidget />
</FeatureGuard>
<FeatureGuard
feature="betaFeatures"
fallback={<div>Beta features coming soon!</div>}
>
<BetaFeaturePanel />
</FeatureGuard>
</div>
);React Admin's advanced features provide powerful capabilities for building sophisticated, production-ready admin applications with comprehensive data management, user customization, performance monitoring, and extensibility options.
Install with Tessl CLI
npx tessl i tessl/npm-react-admin