Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | 2x | import { lazy, Suspense, useMemo, ComponentType } from 'react';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
import Alert from '@mui/material/Alert';
import AlertTitle from '@mui/material/AlertTitle';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import CircularProgress from '@mui/material/CircularProgress';
import Typography from '@mui/material/Typography';
import { getFieldComponent, FieldComponentProps } from './FieldRegistry';
import { useFormContext } from './FormContext';
function FieldErrorFallback({
error,
resetErrorBoundary,
compositeKey,
fieldType,
}: FallbackProps & { compositeKey: string; fieldType: string }) {
return (
<Alert
severity="error"
sx={{ my: 1 }}
action={
<Button color="inherit" size="small" onClick={resetErrorBoundary}>
Retry
</Button>
}
>
<AlertTitle>Field Error</AlertTitle>
<Typography variant="body2" component="div">
Failed to render field: <strong>{compositeKey}</strong>
</Typography>
<Typography variant="body2" color="text.secondary">
Type: {fieldType}
</Typography>
{process.env.NODE_ENV === 'development' && (
<Typography
variant="caption"
component="pre"
sx={{ mt: 1, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}
>
{error instanceof Error ? error.message : String(error)}
</Typography>
)}
</Alert>
);
}
function FieldLoadingFallback() {
return (
<Box sx={{ display: 'flex', alignItems: 'center', p: 1, gap: 1 }}>
<CircularProgress size={16} />
<Typography variant="body2" color="text.secondary">
Loading field...
</Typography>
</Box>
);
}
const componentCache = new Map<string, ComponentType<FieldComponentProps>>();
function getLazyComponent(fieldType: string): ComponentType<FieldComponentProps> {
if (!componentCache.has(fieldType)) {
const importer = getFieldComponent(fieldType);
const LazyComponent = lazy(importer);
componentCache.set(fieldType, LazyComponent);
}
return componentCache.get(fieldType)!;
}
interface FieldRendererProps {
compositeKey: string;
loadingFallback?: React.ReactNode;
}
export function FieldRenderer({ compositeKey, loadingFallback }: FieldRendererProps) {
const form = useFormContext();
const fieldConfig = form.getFieldConfig(compositeKey);
const fieldType = fieldConfig?.type || 'not-found';
const FieldComponent = useMemo(() => getLazyComponent(fieldType), [fieldType]);
return (
<ErrorBoundary
fallbackRender={(props) => (
<FieldErrorFallback {...props} compositeKey={compositeKey} fieldType={fieldType} />
)}
resetKeys={[`${compositeKey}-${fieldType}`]}
>
<Suspense fallback={loadingFallback || <FieldLoadingFallback />}>
<FieldComponent compositeKey={compositeKey} />
</Suspense>
</ErrorBoundary>
);
}
interface FieldListProps {
compositeKeys: string[];
wrapper?: ComponentType<{ children: React.ReactNode }>;
}
export function FieldList({ compositeKeys, wrapper: Wrapper }: FieldListProps) {
if (Wrapper) {
return (
<>
{compositeKeys.map((key) => (
<Wrapper key={key}>
<FieldRenderer compositeKey={key} />
</Wrapper>
))}
</>
);
}
return (
<>
{compositeKeys.map((key) => (
<FieldRenderer key={key} compositeKey={key} />
))}
</>
);
}
export function clearFieldComponentCache(): void {
componentCache.clear();
}
export default FieldRenderer;
|