Component Customization Guide
Learn how to customize React Admin components used in generated dashboard pages.
Overview
TGraph generates React Admin pages with default components from @/components/admin. You can override these components to use your own custom implementations, allowing you to:
- Apply custom styling
- Add business logic
- Integrate third-party component libraries
- Maintain consistent UI across your application
Component Categories
Components are organized into two categories:
- Form Components - Used in Create and Edit pages (inputs)
- Display Components - Used in List and Show pages (fields)
Available Component Overrides
Form Components (Inputs)
| Component | Used For | Default Import |
|---|---|---|
TextInput | String fields | @/components/admin |
NumberInput | Numeric fields | @/components/admin |
BooleanInput | Boolean fields | @/components/admin |
DateTimeInput | DateTime fields | @/components/admin |
SelectInput | Enum fields | @/components/admin |
ReferenceInput | Relation fields (single) | @/components/admin |
ReferenceArrayInput | Relation fields (array) | @/components/admin |
AutocompleteInput | Autocomplete (single) | @/components/admin |
AutocompleteArrayInput | Autocomplete (array) | @/components/admin |
JsonInput | JSON fields | @/components/admin |
FileInput | File uploads | @/components/admin |
UrlInput | URL fields | @/components/admin |
Display Components (Fields)
| Component | Used For | Default Import |
|---|---|---|
TextField | String fields | @/components/admin |
NumberField | Numeric fields | @/components/admin |
BooleanField | Boolean fields | @/components/admin |
DateField | Date fields | @/components/admin |
DateTimeField | DateTime fields | @/components/admin |
SelectField | Enum fields | @/components/admin |
ReferenceField | Relation fields | @/components/admin |
JsonField | JSON fields | @/components/admin |
FileField | File fields | @/components/admin |
UrlField | URL fields | @/components/admin |
Configuration
Override components in your tgraph.config.ts:
export const config: Config = {
// ... other config
dashboard: {
enabled: true,
updateDataProvider: true,
components: {
// Form components (used in Create/Edit pages)
form: {
TextInput: {
name: 'CustomTextInput',
importPath: '@/components/inputs/TextInput',
},
NumberInput: {
name: 'CustomNumberInput',
importPath: '@/components/inputs/NumberInput',
},
},
// Display components (used in List/Show pages)
display: {
TextField: {
name: 'CustomTextField',
importPath: '@/components/fields/TextField',
},
},
},
},
};
Examples
Example 1: Custom Styled Text Input
Create a custom text input with Tailwind styling:
// src/components/inputs/TextInput.tsx
import React from 'react';
import { useInput, InputProps } from 'react-admin';
export const CustomTextInput: React.FC<InputProps> = (props) => {
const { field, fieldState } = useInput(props);
return (
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
{props.label}
</label>
<input
{...field}
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded-md
focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{fieldState.error && (
<p className="mt-1 text-sm text-red-600">{fieldState.error.message}</p>
)}
</div>
);
};
Configure in tgraph.config.ts:
dashboard: {
components: {
form: {
TextInput: {
name: 'CustomTextInput',
importPath: '@/components/inputs/TextInput'
},
},
},
}
Example 2: Third-Party Component Library
Integrate Material-UI components:
// src/components/inputs/NumberInput.tsx
import React from 'react';
import { useInput, InputProps } from 'react-admin';
import { TextField } from '@mui/material';
export const MuiNumberInput: React.FC<InputProps> = (props) => {
const { field, fieldState } = useInput(props);
return (
<TextField
{...field}
type="number"
label={props.label}
error={!!fieldState.error}
helperText={fieldState.error?.message}
fullWidth
margin="normal"
variant="outlined"
/>
);
};
Configure:
dashboard: {
components: {
form: {
NumberInput: {
name: 'MuiNumberInput',
importPath: '@/components/inputs/NumberInput'
},
},
},
}
Example 3: Custom Field with Validation
Add custom validation logic:
// src/components/inputs/EmailInput.tsx
import React from 'react';
import { useInput, InputProps } from 'react-admin';
export const EmailInput: React.FC<InputProps> = (props) => {
const { field, fieldState } = useInput({
...props,
validate: (value) => {
if (!value) return 'Email is required';
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return 'Invalid email format';
}
// Check for company domain
if (value && !value.endsWith('@company.com')) {
return 'Must use company email';
}
return undefined;
},
});
return (
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
{props.label}
</label>
<input
{...field}
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded-md"
placeholder="user@company.com"
/>
{fieldState.error && (
<p className="mt-1 text-sm text-red-600">{fieldState.error.message}</p>
)}
</div>
);
};
Use the text input override for email fields:
dashboard: {
components: {
form: {
TextInput: {
name: 'EmailInput',
importPath: '@/components/inputs/EmailInput'
},
},
},
}
Example 4: Custom Display Field
Create a custom field with formatting:
// src/components/fields/CurrencyField.tsx
import React from 'react';
import { useRecordContext, FieldProps } from 'react-admin';
export const CurrencyField: React.FC<FieldProps> = (props) => {
const record = useRecordContext();
const value = record?.[props.source];
if (value === null || value === undefined) {
return <span className="text-gray-400">—</span>;
}
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(value);
return <span className="font-medium text-green-600">{formatted}</span>;
};
Configure for number fields:
dashboard: {
components: {
display: {
NumberField: {
name: 'CurrencyField',
importPath: '@/components/fields/CurrencyField'
},
},
},
}
Component Requirements
Custom components must follow React Admin’s component interface:
Form Component Requirements
import { InputProps } from 'react-admin';
interface CustomInputProps extends InputProps {
source: string; // Field name
label?: string; // Display label
disabled?: boolean; // Disable input
readOnly?: boolean; // Read-only mode
}
export const CustomInput: React.FC<CustomInputProps> = (props) => {
// Must use useInput hook
const { field, fieldState } = useInput(props);
// Return JSX with field spread
return <input {...field} />;
};
Display Component Requirements
import { FieldProps } from 'react-admin';
interface CustomFieldProps extends FieldProps {
source: string; // Field name
label?: string; // Display label
}
export const CustomField: React.FC<CustomFieldProps> = (props) => {
// Must use useRecordContext hook
const record = useRecordContext();
const value = record?.[props.source];
// Return JSX with value
return <span>{value}</span>;
};
Best Practices
1. Maintain Component Interface
Always accept and use the same props as React Admin components:
// ✅ Good
export const CustomInput: React.FC<InputProps> = (props) => {
const { field, fieldState } = useInput(props);
return <input {...field} />;
};
// ❌ Bad - doesn't accept InputProps
export const CustomInput = () => {
return <input />;
};
2. Handle All Field States
Support all field states (error, touched, dirty):
export const CustomInput: React.FC<InputProps> = (props) => {
const { field, fieldState } = useInput(props);
return (
<div>
<input
{...field}
className={fieldState.error ? 'border-red-500' : 'border-gray-300'}
/>
{fieldState.error && (
<span className="text-red-600">{fieldState.error.message}</span>
)}
{fieldState.dirty && <span className="text-blue-500">Modified</span>}
</div>
);
};
3. Support Disabled and ReadOnly
Respect the disabled and readOnly props:
export const CustomInput: React.FC<InputProps> = (props) => {
const { field } = useInput(props);
return (
<input
{...field}
disabled={props.disabled}
readOnly={props.readOnly}
/>
);
};
4. Provide Consistent Styling
Maintain visual consistency across all custom components:
// Create a shared styles object
const inputStyles = {
base: 'w-full px-3 py-2 border rounded-md',
normal: 'border-gray-300',
error: 'border-red-500',
disabled: 'bg-gray-100 cursor-not-allowed',
};
export const CustomInput: React.FC<InputProps> = (props) => {
const { field, fieldState } = useInput(props);
const className = [
inputStyles.base,
fieldState.error ? inputStyles.error : inputStyles.normal,
props.disabled ? inputStyles.disabled : '',
].join(' ');
return <input {...field} className={className} />;
};
5. Test with Different Field Types
Test custom components with various field configurations:
// Test with required fields
<CustomInput source="name" label="Name" required />
// Test with optional fields
<CustomInput source="nickname" label="Nickname" />
// Test with disabled state
<CustomInput source="id" label="ID" disabled />
// Test with default values
<CustomInput source="status" label="Status" defaultValue="active" />
Troubleshooting
Component Not Being Used
If your custom component isn’t being used after configuration:
- Check the import path - Ensure it matches your project structure
- Verify the component name - Must match the exported name
- Regenerate pages - Run
tgraph dashboardto apply changes - Check console - Look for import errors in browser console
TypeScript Errors
If you get TypeScript errors:
// Add proper type imports
import type { InputProps } from 'react-admin';
// Extend props if needed
interface CustomInputProps extends InputProps {
customProp?: string;
}
export const CustomInput: React.FC<CustomInputProps> = (props) => {
// ...
};
Component Not Rendering
If the component renders but doesn’t work:
- Check useInput hook - Must be called with props
- Spread field object -
<input {...field} /> - Check value handling - Some inputs need special handling (checkboxes, selects)
Advanced Usage
Conditional Component Logic
export const ConditionalInput: React.FC<InputProps> = (props) => {
const { field, fieldState } = useInput(props);
const record = useRecordContext();
// Show different input based on another field
if (record?.type === 'email') {
return <input {...field} type="email" />;
}
return <input {...field} type="text" />;
};
Composite Components
export const AddressInput: React.FC<InputProps> = (props) => {
return (
<div className="space-y-2">
<TextInput source={`${props.source}.street`} label="Street" />
<TextInput source={`${props.source}.city`} label="City" />
<TextInput source={`${props.source}.zip`} label="ZIP" />
</div>
);
};
Related
- Configuration Reference - Full config options
- Authentication Guards - Configure guards
- Field Directives - Control field generation