typescript-code-review

TypeScript/React Code Review Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "typescript-code-review" with this command: npx skills add jpoutrin/product-forge/jpoutrin-product-forge-typescript-code-review

TypeScript/React Code Review Patterns

This skill provides TypeScript and React-specific code review guidelines. Use alongside typescript-style for comprehensive review.

Critical Security Issues

XSS Vulnerabilities

// VULNERABLE - dangerouslySetInnerHTML without sanitization <div dangerouslySetInnerHTML={{ __html: userInput }} />

// VULNERABLE - innerHTML assignment element.innerHTML = userContent;

// SAFE - use DOMPurify import DOMPurify from 'dompurify'; <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />

// SAFER - avoid innerHTML entirely <div>{userContent}</div> // React auto-escapes

Insecure Data Exposure

// VULNERABLE - logging sensitive data console.log("User data:", user); // May include tokens, passwords console.log("Request:", request.headers); // Auth headers

// VULNERABLE - exposing in error messages throw new Error(Auth failed for ${credentials});

// SAFE - sanitize before logging console.log("User ID:", user.id); // Only necessary fields

Unsafe eval/Function Constructor

// VULNERABLE - code execution from strings eval(userInput); new Function(userInput)(); setTimeout(userInput, 1000); // String argument

// SAFE - avoid string evaluation setTimeout(() => { processInput(userInput); }, 1000);

Missing CSRF Protection

// VULNERABLE - no CSRF token fetch('/api/delete', { method: 'POST', body: data });

// SAFE - include CSRF token fetch('/api/delete', { method: 'POST', headers: { 'X-CSRF-Token': csrfToken }, body: data, });

High Priority Type Safety Issues

Unsafe Type Assertions

// DANGEROUS - bypasses type checking const user = data as User; const items = response as any[];

// SAFER - use type guards function isUser(data: unknown): data is User { return typeof data === 'object' && data !== null && 'id' in data; }

if (isUser(data)) { // data is now typed as User }

Non-null Assertion Abuse

// DANGEROUS - runtime error if null const name = user!.profile!.name!;

// SAFE - explicit null handling const name = user?.profile?.name ?? 'Unknown';

// Or with explicit checks if (user?.profile?.name) { const name = user.profile.name; }

Missing Error Handling in Async Code

// BUG - unhandled promise rejection async function fetchData() { const response = await fetch(url); return response.json(); // What if fetch fails? }

// CORRECT - handle errors async function fetchData() { try { const response = await fetch(url); if (!response.ok) { throw new Error(HTTP ${response.status}); } return response.json(); } catch (error) { console.error('Fetch failed:', error); throw error; // Re-throw or return default } }

Ignoring Promise Results

// BUG - fire and forget saveUser(userData); // No await, no error handling

// CORRECT await saveUser(userData); // or saveUser(userData).catch(console.error);

React Anti-Patterns

Missing useEffect Dependencies

// BUG - stale closure, missing dependency useEffect(() => { fetchUser(userId); // userId not in deps! }, []); // Empty deps = runs once with initial userId

// CORRECT - include all dependencies useEffect(() => { fetchUser(userId); }, [userId]);

State Updates on Unmounted Components

// BUG - memory leak, state update after unmount useEffect(() => { fetchData().then(setData); }, []);

// CORRECT - cleanup with flag or AbortController useEffect(() => { let mounted = true; fetchData().then((data) => { if (mounted) setData(data); }); return () => { mounted = false; }; }, []);

// BETTER - use AbortController useEffect(() => { const controller = new AbortController(); fetch(url, { signal: controller.signal }) .then(res => res.json()) .then(setData) .catch(err => { if (err.name !== 'AbortError') throw err; }); return () => controller.abort(); }, [url]);

Missing Key Prop in Lists

// BUG - using index as key (causes issues on reorder) items.map((item, index) => <Item key={index} data={item} />)

// CORRECT - use stable unique identifier items.map((item) => <Item key={item.id} data={item} />)

Inline Functions in JSX (Performance)

// PERFORMANCE - new function on every render <Button onClick={() => handleClick(id)} />

// BETTER - memoize if passed to memoized child const handleButtonClick = useCallback(() => handleClick(id), [id]); <MemoizedButton onClick={handleButtonClick} />

Props Drilling Deep

// CODE SMELL - passing props through many levels <App user={user}> <Layout user={user}> <Sidebar user={user}> <UserProfile user={user} />

// BETTER - use Context for cross-cutting concerns const UserContext = createContext<User | null>(null); <UserContext.Provider value={user}> <App /> </UserContext.Provider>

Performance Anti-Patterns

Re-rendering Entire Lists

// SLOW - all items re-render when one changes function ItemList({ items }: { items: Item[] }) { return items.map(item => <ItemRow item={item} />); }

// FAST - memoize individual items const MemoizedItemRow = memo(ItemRow); function ItemList({ items }: { items: Item[] }) { return items.map(item => <MemoizedItemRow key={item.id} item={item} />); }

Expensive Calculations Without Memoization

// SLOW - recalculates on every render function ExpensiveComponent({ data }: { data: number[] }) { const sorted = [...data].sort((a, b) => a - b); // Every render! const sum = data.reduce((a, b) => a + b, 0); // Every render! return <div>{sorted.join(',')}: {sum}</div>; }

// FAST - memoize expensive operations function ExpensiveComponent({ data }: { data: number[] }) { const sorted = useMemo(() => [...data].sort((a, b) => a - b), [data]); const sum = useMemo(() => data.reduce((a, b) => a + b, 0), [data]); return <div>{sorted.join(',')}: {sum}</div>; }

Unnecessary State

// OVERHEAD - derived state should be computed const [items, setItems] = useState<Item[]>([]); const [itemCount, setItemCount] = useState(0); // Unnecessary!

// Update both when items change - easy to forget setItems(newItems); setItemCount(newItems.length); // Must keep in sync

// BETTER - derive from source of truth const [items, setItems] = useState<Item[]>([]); const itemCount = items.length; // Always in sync

Large Bundle Imports

// HEAVY - imports entire library import _ from 'lodash'; import moment from 'moment'; // 300KB+

// LIGHT - import specific functions import debounce from 'lodash/debounce'; import { format } from 'date-fns'; // Much smaller

Accessibility Issues

Missing ARIA Labels

// INACCESSIBLE - icon-only button without label <button onClick={onClose}> <CloseIcon /> </button>

// ACCESSIBLE <button onClick={onClose} aria-label="Close dialog"> <CloseIcon /> </button>

Non-Interactive Elements with Handlers

// INACCESSIBLE - div with click handler <div onClick={handleClick}>Click me</div>

// ACCESSIBLE - use button or add role/keyboard <button onClick={handleClick}>Click me</button> // or <div role="button" tabIndex={0} onClick={handleClick} onKeyDown={(e) => e.key === 'Enter' && handleClick()}

Click me </div>

Missing Form Labels

// INACCESSIBLE - input without label <input type="email" placeholder="Email" />

// ACCESSIBLE <label> Email <input type="email" /> </label> // or <label htmlFor="email">Email</label> <input id="email" type="email" />

Color-Only Indicators

// INACCESSIBLE - status only indicated by color <span style={{ color: isError ? 'red' : 'green' }}> {status} </span>

// ACCESSIBLE - include text or icon <span style={{ color: isError ? 'red' : 'green' }}> {isError ? '❌ ' : '✓ '}{status} </span>

Common TypeScript Mistakes

Improper Null Checking

// BUG - falsy check catches 0 and '' if (!value) return; // Fails for value = 0 or ''

// CORRECT - explicit null/undefined check if (value == null) return; // Catches null and undefined // or if (value === null || value === undefined) return;

Object Spread Overwrites

// BUG - order matters, later overwrites earlier const config = { ...defaultConfig, ...userConfig, timeout: 5000 }; // timeout is always 5000, even if userConfig.timeout was set!

// CORRECT - put defaults first const config = { timeout: 5000, ...defaultConfig, ...userConfig };

Array Method Return Values

// BUG - forEach doesn't return const doubled = items.forEach(x => x * 2); // undefined!

// CORRECT - use map for transformation const doubled = items.map(x => x * 2);

// BUG - filter callback should return boolean const active = items.filter(item => item.status); // Works but unclear

// CORRECT - explicit boolean return const active = items.filter(item => item.status === 'active');

Test Coverage Gaps

Flag missing tests for:

  • Error states: Network failures, invalid inputs, edge cases

  • User interactions: Button clicks, form submissions, keyboard navigation

  • Async behavior: Loading states, success/error handling

  • Accessibility: Screen reader compatibility, keyboard navigation

  • Edge cases: Empty arrays, null values, boundary conditions

// Missing test coverage examples it('should show error message on fetch failure', async () => {}); it('should be keyboard navigable', () => {}); it('should handle empty item list', () => {}); it('should show loading state while fetching', () => {});

Code Review Checklist

Security

  • No XSS vectors (dangerouslySetInnerHTML, innerHTML)

  • No sensitive data in logs or error messages

  • No eval or dynamic code execution

  • CSRF protection on mutations

Type Safety

  • No any types (use unknown
  • type guards)
  • No unsafe type assertions without validation

  • Proper null/undefined handling

  • All promises handled (await or .catch)

React Patterns

  • useEffect dependencies complete

  • Cleanup functions for subscriptions/timers

  • Stable keys in lists (not array index)

  • No unnecessary state (derive when possible)

Performance

  • Expensive operations memoized (useMemo)

  • Event handlers memoized when needed (useCallback)

  • Large components use React.memo

  • Tree-shakeable imports

Accessibility

  • Interactive elements have labels

  • Keyboard navigation works

  • Form inputs have associated labels

  • Status not conveyed by color alone

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

typescript-import-style

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-review

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-style

No summary provided by upstream source.

Repository SourceNeeds Review