Opinionated frontend development standards for modern React + TypeScript applications. Covers Suspense-first data fetching, lazy loading, feature-based architecture, MUI v7 styling, TanStack Router, performance optimization, and strict TypeScript practices.
67
67%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Modern React component architecture for the application emphasizing type safety, lazy loading, and Suspense boundaries.
All components use the React.FC<Props> pattern for:
import React from 'react';
interface MyComponentProps {
/** User ID to display */
userId: number;
/** Optional callback when action occurs */
onAction?: () => void;
}
export const MyComponent: React.FC<MyComponentProps> = ({ userId, onAction }) => {
return (
<div>
User: {userId}
</div>
);
};
export default MyComponent;Key Points:
React.FC<Props> provides type safetyLazy load components that are:
import React from 'react';
// Lazy load heavy component
const PostDataGrid = React.lazy(() =>
import('./grids/PostDataGrid')
);
// For named exports
const MyComponent = React.lazy(() =>
import('./MyComponent').then(module => ({
default: module.MyComponent
}))
);Example from PostTable.tsx:
/**
* Main post table container component
*/
import React, { useState, useCallback } from 'react';
import { Box, Paper } from '@mui/material';
// Lazy load PostDataGrid to optimize bundle size
const PostDataGrid = React.lazy(() => import('./grids/PostDataGrid'));
import { SuspenseLoader } from '~components/SuspenseLoader';
export const PostTable: React.FC<PostTableProps> = ({ formId }) => {
return (
<Box>
<SuspenseLoader>
<PostDataGrid formId={formId} />
</SuspenseLoader>
</Box>
);
};
export default PostTable;Import:
import { SuspenseLoader } from '~components/SuspenseLoader';
// Or
import { SuspenseLoader } from '@/components/SuspenseLoader';Usage:
<SuspenseLoader>
<LazyLoadedComponent />
</SuspenseLoader>What it does:
Route Level:
// routes/my-route/index.tsx
const MyPage = lazy(() => import('@/features/my-feature/components/MyPage'));
function Route() {
return (
<SuspenseLoader>
<MyPage />
</SuspenseLoader>
);
}Component Level:
function ParentComponent() {
return (
<Box>
<Header />
<SuspenseLoader>
<HeavyDataGrid />
</SuspenseLoader>
</Box>
);
}Multiple Boundaries:
function Page() {
return (
<Box>
<SuspenseLoader>
<HeaderSection />
</SuspenseLoader>
<SuspenseLoader>
<MainContent />
</SuspenseLoader>
<SuspenseLoader>
<Sidebar />
</SuspenseLoader>
</Box>
);
}Each section loads independently, better UX.
/**
* Component description
* What it does, when to use it
*/
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { Box, Paper, Button } from '@mui/material';
import type { SxProps, Theme } from '@mui/material';
import { useSuspenseQuery } from '@tanstack/react-query';
// Feature imports
import { myFeatureApi } from '../api/myFeatureApi';
import type { MyData } from '~types/myData';
// Component imports
import { SuspenseLoader } from '~components/SuspenseLoader';
// Hooks
import { useAuth } from '@/hooks/useAuth';
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
// 1. PROPS INTERFACE (with JSDoc)
interface MyComponentProps {
/** The ID of the entity to display */
entityId: number;
/** Optional callback when action completes */
onComplete?: () => void;
/** Display mode */
mode?: 'view' | 'edit';
}
// 2. STYLES (if inline and <100 lines)
const componentStyles: Record<string, SxProps<Theme>> = {
container: {
p: 2,
display: 'flex',
flexDirection: 'column',
},
header: {
mb: 2,
display: 'flex',
justifyContent: 'space-between',
},
};
// 3. COMPONENT DEFINITION
export const MyComponent: React.FC<MyComponentProps> = ({
entityId,
onComplete,
mode = 'view',
}) => {
// 4. HOOKS (in this order)
// - Context hooks first
const { user } = useAuth();
const { showSuccess, showError } = useMuiSnackbar();
// - Data fetching
const { data } = useSuspenseQuery({
queryKey: ['myEntity', entityId],
queryFn: () => myFeatureApi.getEntity(entityId),
});
// - Local state
const [selectedItem, setSelectedItem] = useState<string | null>(null);
const [isEditing, setIsEditing] = useState(mode === 'edit');
// - Memoized values
const filteredData = useMemo(() => {
return data.filter(item => item.active);
}, [data]);
// - Effects
useEffect(() => {
// Setup
return () => {
// Cleanup
};
}, []);
// 5. EVENT HANDLERS (with useCallback)
const handleItemSelect = useCallback((itemId: string) => {
setSelectedItem(itemId);
}, []);
const handleSave = useCallback(async () => {
try {
await myFeatureApi.updateEntity(entityId, { /* data */ });
showSuccess('Entity updated successfully');
onComplete?.();
} catch (error) {
showError('Failed to update entity');
}
}, [entityId, onComplete, showSuccess, showError]);
// 6. RENDER
return (
<Box sx={componentStyles.container}>
<Box sx={componentStyles.header}>
<h2>My Component</h2>
<Button onClick={handleSave}>Save</Button>
</Box>
<Paper sx={{ p: 2 }}>
{filteredData.map(item => (
<div key={item.id}>{item.name}</div>
))}
</Paper>
</Box>
);
};
// 7. EXPORT (default export at bottom)
export default MyComponent;Split into multiple components when:
Example:
// ❌ AVOID - Monolithic
function MassiveComponent() {
// 500+ lines
// Search logic
// Filter logic
// Grid logic
// Action panel logic
}
// ✅ PREFERRED - Modular
function ParentContainer() {
return (
<Box>
<SearchAndFilter onFilter={handleFilter} />
<DataGrid data={filteredData} />
<ActionPanel onAction={handleAction} />
</Box>
);
}Keep in same file when:
export const MyComponent: React.FC<Props> = ({ ... }) => {
// Component logic
};
export default MyComponent;Why:
const MyComponent = React.lazy(() =>
import('./MyComponent').then(module => ({
default: module.MyComponent
}))
);// Parent
function Parent() {
const [selectedId, setSelectedId] = useState<string | null>(null);
return (
<Child
data={data} // Props down
onSelect={setSelectedId} // Events up
/>
);
}
// Child
interface ChildProps {
data: Data[];
onSelect: (id: string) => void;
}
export const Child: React.FC<ChildProps> = ({ data, onSelect }) => {
return (
<div onClick={() => onSelect(data[0].id)}>
{/* Content */}
</div>
);
};Use context for deep nesting:
// ❌ AVOID - Prop drilling 5+ levels
<A prop={x}>
<B prop={x}>
<C prop={x}>
<D prop={x}>
<E prop={x} /> // Finally uses it here
</D>
</C>
</B>
</A>
// ✅ PREFERRED - Context or TanStack Query
const MyContext = createContext<MyData | null>(null);
function Provider({ children }) {
const { data } = useSuspenseQuery({ ... });
return <MyContext.Provider value={data}>{children}</MyContext.Provider>;
}
function DeepChild() {
const data = useContext(MyContext);
// Use data directly
}// Card.tsx
export const Card: React.FC<CardProps> & {
Header: typeof CardHeader;
Body: typeof CardBody;
Footer: typeof CardFooter;
} = ({ children }) => {
return <Paper>{children}</Paper>;
};
Card.Header = CardHeader;
Card.Body = CardBody;
Card.Footer = CardFooter;
// Usage
<Card>
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
<Card.Footer>Actions</Card.Footer>
</Card>interface DataProviderProps {
children: (data: Data) => React.ReactNode;
}
export const DataProvider: React.FC<DataProviderProps> = ({ children }) => {
const { data } = useSuspenseQuery({ ... });
return <>{children(data)}</>;
};
// Usage
<DataProvider>
{(data) => <Display data={data} />}
</DataProvider>Modern Component Recipe:
React.FC<Props> with TypeScriptReact.lazy(() => import())<SuspenseLoader> for loadinguseSuspenseQuery for datauseCallbackSee Also: