// Enforces React and TypeScript best practices, detects anti-patterns, and ensures code quality. Use when reviewing components, refactoring code, or validating implementations. Triggers on code review requests, quality checks, or React-specific questions.
| name | react-quality |
| description | Enforces React and TypeScript best practices, detects anti-patterns, and ensures code quality. Use when reviewing components, refactoring code, or validating implementations. Triggers on code review requests, quality checks, or React-specific questions. |
Enforce best practices and maintain high code quality in React/TypeScript applications.
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
any types (use unknown if needed)// Good: Typed functional component
interface ButtonProps {
children: React.ReactNode;
variant?: 'primary' | 'secondary';
onClick?: () => void;
disabled?: boolean;
}
export const Button: React.FC<ButtonProps> = ({
children,
variant = 'primary',
onClick,
disabled = false,
}) => {
// Component logic
return (
<button
onClick={onClick}
disabled={disabled}
className={getVariantClasses(variant)}
>
{children}
</button>
);
};
React.ReactNode// BAD
{items.map((item, index) => (
<Item key={index} {...item} />
))}
// GOOD
{items.map((item) => (
<Item key={item.id} {...item} />
))}
// BAD - Creates new object every render
<Component style={{ color: 'red' }} />
// GOOD - Stable reference
const style = { color: 'red' };
<Component style={style} />
// OR use useMemo for dynamic values
const style = useMemo(() => ({ color }), [color]);
// BAD - Missing dependency
useEffect(() => {
fetchData(userId);
}, []); // userId should be in deps
// GOOD
useEffect(() => {
fetchData(userId);
}, [userId]);
// BAD - Causes infinite loop
const [count, setCount] = useState(0);
setCount(count + 1); // In render body!
// GOOD - In event handler or effect
const handleClick = () => setCount(count + 1);
// BAD
document.getElementById('myDiv').innerHTML = 'Hello';
// GOOD - Use refs
const divRef = useRef<HTMLDivElement>(null);
// ... use divRef.current
// Prefer derived state over multiple states
// BAD
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
// GOOD
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = `${firstName} ${lastName}`;
// Always clean up side effects
useEffect(() => {
const subscription = subscribe();
return () => subscription.unsubscribe();
}, []);
// Split unrelated effects
useEffect(() => { /* fetch data */ }, [id]);
useEffect(() => { /* track analytics */ }, [page]);
// Use for expensive calculations
const sortedList = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// Use for stable callback references
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
// Extract reusable logic
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('Error:', error, info);
}
render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return this.props.children;
}
}
const [error, setError] = useState<Error | null>(null);
const [loading, setLoading] = useState(false);
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const data = await api.get('/endpoint');
setData(data);
} catch (err) {
setError(err instanceof Error ? err : new Error('Unknown error'));
} finally {
setLoading(false);
}
};
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}
// Use react-window or similar for 100+ items
import { FixedSizeList } from 'react-window';
<FixedSizeList
height={400}
itemCount={items.length}
itemSize={50}
>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList>
const ExpensiveComponent = React.memo(({ data }) => {
// Only re-renders when data changes
return <div>{/* complex rendering */}</div>
});
// BAD - XSS vulnerability
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// GOOD - Sanitize first
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(userContent)
}} />
// BEST - Use text content
<div>{userContent}</div>
// Never expose secrets to client
// BAD
const apiKey = process.env.API_KEY;
// GOOD - Only VITE_ prefixed vars are exposed
const publicKey = import.meta.env.VITE_PUBLIC_KEY;
components/
├── Button/
│ ├── Button.tsx # Component
│ ├── Button.test.tsx # Tests
│ ├── Button.stories.tsx # Storybook
│ └── index.ts # Export
// components/Button/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button';
any types