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
CRITICAL: Proper loading and error state handling prevents layout shift and provides better user experience.
// ❌ NEVER DO THIS - Early return with loading spinner
const Component = () => {
const { data, isLoading } = useQuery();
// WRONG: This causes layout shift and poor UX
if (isLoading) {
return <LoadingSpinner />;
}
return <Content data={data} />;
};Why this is bad:
Option 1: SuspenseLoader (PREFERRED for new components)
import { SuspenseLoader } from '~components/SuspenseLoader';
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
export const MyComponent: React.FC = () => {
return (
<SuspenseLoader>
<HeavyComponent />
</SuspenseLoader>
);
};Option 2: LoadingOverlay (for legacy useQuery patterns)
import { LoadingOverlay } from '~components/LoadingOverlay';
export const MyComponent: React.FC = () => {
const { data, isLoading } = useQuery({ ... });
return (
<LoadingOverlay loading={isLoading}>
<Content data={data} />
</LoadingOverlay>
);
};import { SuspenseLoader } from '~components/SuspenseLoader';
// Or
import { SuspenseLoader } from '@/components/SuspenseLoader';<SuspenseLoader>
<LazyLoadedComponent />
</SuspenseLoader>import { useSuspenseQuery } from '@tanstack/react-query';
import { SuspenseLoader } from '~components/SuspenseLoader';
const Inner: React.FC = () => {
// No isLoading needed!
const { data } = useSuspenseQuery({
queryKey: ['data'],
queryFn: () => api.getData(),
});
return <Display data={data} />;
};
// Outer component wraps in Suspense
export const Outer: React.FC = () => {
return (
<SuspenseLoader>
<Inner />
</SuspenseLoader>
);
};Pattern: Separate loading for independent sections
export const Dashboard: React.FC = () => {
return (
<Box>
<SuspenseLoader>
<Header />
</SuspenseLoader>
<SuspenseLoader>
<MainContent />
</SuspenseLoader>
<SuspenseLoader>
<Sidebar />
</SuspenseLoader>
</Box>
);
};Benefits:
export const ParentComponent: React.FC = () => {
return (
<SuspenseLoader>
{/* Parent suspends while loading */}
<ParentContent>
<SuspenseLoader>
{/* Nested suspense for child */}
<ChildComponent />
</SuspenseLoader>
</ParentContent>
</SuspenseLoader>
);
};useQuery (not refactored to Suspense yet)import { LoadingOverlay } from '~components/LoadingOverlay';
export const MyComponent: React.FC = () => {
const { data, isLoading } = useQuery({
queryKey: ['data'],
queryFn: () => api.getData(),
});
return (
<LoadingOverlay loading={isLoading}>
<Box sx={{ p: 2 }}>
{data && <Content data={data} />}
</Box>
</LoadingOverlay>
);
};What it does:
NEVER use react-toastify - Project standard is MUI Snackbar
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
export const MyComponent: React.FC = () => {
const { showSuccess, showError, showInfo, showWarning } = useMuiSnackbar();
const handleAction = async () => {
try {
await api.doSomething();
showSuccess('Operation completed successfully');
} catch (error) {
showError('Operation failed');
}
};
return <Button onClick={handleAction}>Do Action</Button>;
};Available Methods:
showSuccess(message) - Green success messageshowError(message) - Red error messageshowWarning(message) - Orange warning messageshowInfo(message) - Blue info messageimport { useSuspenseQuery } from '@tanstack/react-query';
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
export const MyComponent: React.FC = () => {
const { showError } = useMuiSnackbar();
const { data } = useSuspenseQuery({
queryKey: ['data'],
queryFn: () => api.getData(),
// Handle errors
onError: (error) => {
showError('Failed to load data');
console.error('Query error:', error);
},
});
return <Content data={data} />;
};import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<Box sx={{ p: 4, textAlign: 'center' }}>
<Typography variant='h5' color='error'>
Something went wrong
</Typography>
<Typography>{error.message}</Typography>
<Button onClick={resetErrorBoundary}>Try Again</Button>
</Box>
);
}
export const MyPage: React.FC = () => {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error) => console.error('Boundary caught:', error)}
>
<SuspenseLoader>
<ComponentThatMightError />
</SuspenseLoader>
</ErrorBoundary>
);
};import React from 'react';
import { Box, Paper } from '@mui/material';
import { useSuspenseQuery } from '@tanstack/react-query';
import { SuspenseLoader } from '~components/SuspenseLoader';
import { myFeatureApi } from '../api/myFeatureApi';
// Inner component uses useSuspenseQuery
const InnerComponent: React.FC<{ id: number }> = ({ id }) => {
const { data } = useSuspenseQuery({
queryKey: ['entity', id],
queryFn: () => myFeatureApi.getEntity(id),
});
// data is always defined - no isLoading needed!
return (
<Paper sx={{ p: 2 }}>
<h2>{data.title}</h2>
<p>{data.description}</p>
</Paper>
);
};
// Outer component provides Suspense boundary
export const OuterComponent: React.FC<{ id: number }> = ({ id }) => {
return (
<Box>
<SuspenseLoader>
<InnerComponent id={id} />
</SuspenseLoader>
</Box>
);
};
export default OuterComponent;import React from 'react';
import { Box } from '@mui/material';
import { useQuery } from '@tanstack/react-query';
import { LoadingOverlay } from '~components/LoadingOverlay';
import { myFeatureApi } from '../api/myFeatureApi';
export const LegacyComponent: React.FC<{ id: number }> = ({ id }) => {
const { data, isLoading, error } = useQuery({
queryKey: ['entity', id],
queryFn: () => myFeatureApi.getEntity(id),
});
return (
<LoadingOverlay loading={isLoading}>
<Box sx={{ p: 2 }}>
{error && <ErrorDisplay error={error} />}
{data && <Content data={data} />}
</Box>
</LoadingOverlay>
);
};import React from 'react';
import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Button } from '@mui/material';
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
import { myFeatureApi } from '../api/myFeatureApi';
export const EntityEditor: React.FC<{ id: number }> = ({ id }) => {
const queryClient = useQueryClient();
const { showSuccess, showError } = useMuiSnackbar();
const { data } = useSuspenseQuery({
queryKey: ['entity', id],
queryFn: () => myFeatureApi.getEntity(id),
onError: () => {
showError('Failed to load entity');
},
});
const updateMutation = useMutation({
mutationFn: (updates) => myFeatureApi.update(id, updates),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['entity', id] });
showSuccess('Entity updated successfully');
},
onError: () => {
showError('Failed to update entity');
},
});
return (
<Button onClick={() => updateMutation.mutate({ name: 'New' })}>
Update
</Button>
);
};// ❌ NEVER - Early return
if (isLoading) {
return <CircularProgress />;
}
// ❌ NEVER - Conditional rendering
{isLoading ? <Spinner /> : <Content />}
// ❌ NEVER - Layout changes
if (isLoading) {
return (
<Box sx={{ height: 100 }}>
<Spinner />
</Box>
);
}
return (
<Box sx={{ height: 500 }}> // Different height!
<Content />
</Box>
);// ✅ BEST - useSuspenseQuery + SuspenseLoader
<SuspenseLoader>
<ComponentWithSuspenseQuery />
</SuspenseLoader>
// ✅ ACCEPTABLE - LoadingOverlay
<LoadingOverlay loading={isLoading}>
<Content />
</LoadingOverlay>
// ✅ OK - Inline skeleton with same layout
<Box sx={{ height: 500 }}>
{isLoading ? <Skeleton variant='rectangular' height='100%' /> : <Content />}
</Box>import { Skeleton, Box } from '@mui/material';
export const MyComponent: React.FC = () => {
const { data, isLoading } = useQuery({ ... });
return (
<Box sx={{ p: 2 }}>
{isLoading ? (
<>
<Skeleton variant='text' width={200} height={40} />
<Skeleton variant='rectangular' width='100%' height={200} />
<Skeleton variant='text' width='100%' />
</>
) : (
<>
<Typography variant='h5'>{data.title}</Typography>
<img src={data.image} />
<Typography>{data.description}</Typography>
</>
)}
</Box>
);
};Key: Skeleton must have same layout as actual content (no shift)
Loading States:
Error Handling:
See Also: