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
Patterns for optimizing React component performance, preventing unnecessary re-renders, and avoiding memory leaks.
import { useMemo } from 'react';
export const DataDisplay: React.FC<{ items: Item[], searchTerm: string }> = ({
items,
searchTerm,
}) => {
// ❌ AVOID - Runs on every render
const filteredItems = items
.filter(item => item.name.includes(searchTerm))
.sort((a, b) => a.name.localeCompare(b.name));
// ✅ CORRECT - Memoized, only recalculates when dependencies change
const filteredItems = useMemo(() => {
return items
.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase()))
.sort((a, b) => a.name.localeCompare(b.name));
}, [items, searchTerm]);
return <List items={filteredItems} />;
};When to use useMemo:
When NOT to use useMemo:
// ❌ AVOID - Creates new function on every render
export const Parent: React.FC = () => {
const handleClick = (id: string) => {
console.log('Clicked:', id);
};
// Child re-renders every time Parent renders
// because handleClick is a new function reference each time
return <Child onClick={handleClick} />;
};import { useCallback } from 'react';
export const Parent: React.FC = () => {
// ✅ CORRECT - Stable function reference
const handleClick = useCallback((id: string) => {
console.log('Clicked:', id);
}, []); // Empty deps = function never changes
// Child only re-renders when props actually change
return <Child onClick={handleClick} />;
};When to use useCallback:
When NOT to use useCallback:
onClick={() => doSomething()}import React from 'react';
interface ExpensiveComponentProps {
data: ComplexData;
onAction: () => void;
}
// ✅ Wrap expensive components in React.memo
export const ExpensiveComponent = React.memo<ExpensiveComponentProps>(
function ExpensiveComponent({ data, onAction }) {
// Complex rendering logic
return <ComplexVisualization data={data} />;
}
);When to use React.memo:
When NOT to use React.memo:
import { useState } from 'react';
import { useDebounce } from 'use-debounce';
import { useSuspenseQuery } from '@tanstack/react-query';
export const SearchComponent: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
// Debounce for 300ms
const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
// Query uses debounced value
const { data } = useSuspenseQuery({
queryKey: ['search', debouncedSearchTerm],
queryFn: () => api.search(debouncedSearchTerm),
enabled: debouncedSearchTerm.length > 0,
});
return (
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder='Search...'
/>
);
};Optimal Debounce Timing:
import { useEffect, useState } from 'react';
export const MyComponent: React.FC = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// ✅ CORRECT - Cleanup interval
const intervalId = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => {
clearInterval(intervalId); // Cleanup!
};
}, []);
useEffect(() => {
// ✅ CORRECT - Cleanup timeout
const timeoutId = setTimeout(() => {
console.log('Delayed action');
}, 5000);
return () => {
clearTimeout(timeoutId); // Cleanup!
};
}, []);
return <div>{count}</div>;
};useEffect(() => {
const handleResize = () => {
console.log('Resized');
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize); // Cleanup!
};
}, []);useEffect(() => {
const abortController = new AbortController();
fetch('/api/data', { signal: abortController.signal })
.then(response => response.json())
.then(data => setState(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
}
});
return () => {
abortController.abort(); // Cleanup!
};
}, []);Note: With TanStack Query, this is handled automatically.
import { useForm } from 'react-hook-form';
export const MyForm: React.FC = () => {
const { register, watch, handleSubmit } = useForm();
// ❌ AVOID - Watches all fields, re-renders on any change
const formValues = watch();
// ✅ CORRECT - Watch only what you need
const username = watch('username');
const email = watch('email');
// Or multiple specific fields
const [username, email] = watch(['username', 'email']);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('username')} />
<input {...register('email')} />
<input {...register('password')} />
{/* Only re-renders when username/email change */}
<p>Username: {username}, Email: {email}</p>
</form>
);
};// ✅ CORRECT - Stable unique keys
{items.map(item => (
<ListItem key={item.id}>
{item.name}
</ListItem>
))}
// ❌ AVOID - Index as key (unstable if list changes)
{items.map((item, index) => (
<ListItem key={index}> // WRONG if list reorders
{item.name}
</ListItem>
))}const ListItem = React.memo<ListItemProps>(({ item, onAction }) => {
return (
<Box onClick={() => onAction(item.id)}>
{item.name}
</Box>
);
});
export const List: React.FC<{ items: Item[] }> = ({ items }) => {
const handleAction = useCallback((id: string) => {
console.log('Action:', id);
}, []);
return (
<Box>
{items.map(item => (
<ListItem
key={item.id}
item={item}
onAction={handleAction}
/>
))}
</Box>
);
};// ❌ AVOID - Component recreated on every render
export const Parent: React.FC = () => {
// New component definition each render!
const ChildComponent = () => <div>Child</div>;
return <ChildComponent />; // Unmounts and remounts every render
};// ✅ CORRECT - Define outside or use useMemo
const ChildComponent: React.FC = () => <div>Child</div>;
export const Parent: React.FC = () => {
return <ChildComponent />; // Stable component
};
// ✅ OR if dynamic, use useMemo
export const Parent: React.FC<{ config: Config }> = ({ config }) => {
const DynamicComponent = useMemo(() => {
return () => <div>{config.title}</div>;
}, [config.title]);
return <DynamicComponent />;
};// ❌ AVOID - Import heavy libraries at top level
import jsPDF from 'jspdf'; // Large library loaded immediately
import * as XLSX from 'xlsx'; // Large library loaded immediately
// ✅ CORRECT - Dynamic import when needed
const handleExportPDF = async () => {
const { jsPDF } = await import('jspdf');
const doc = new jsPDF();
// Use it
};
const handleExportExcel = async () => {
const XLSX = await import('xlsx');
// Use it
};Performance Checklist:
useMemo for expensive computations (filter, sort, map)useCallback for functions passed to childrenReact.memo for expensive componentsSee Also: