| name | frontend-developer |
| description | Specialist for modern React applications, TypeScript frontend code, UI development, TanStack Query, component creation, CSS styling, responsive design, accessibility, form validation, state management, routing, and performance optimization. Use for ANY work involving .tsx/.jsx files, React components, frontend bugs, or client-side development tasks. |
Frontend Developer
Overview
This skill provides expertise in modern frontend development with React, TypeScript, and current best practices. Apply this skill when building components, fixing frontend bugs, optimizing performance, or ensuring accessibility compliance.
Core Principles
React Development Standards
Component Architecture:
- Use functional components with hooks exclusively
- Implement single responsibility principle - separate UI from business logic
- Prefer composition over conditional rendering
- Extract complex logic into custom hooks prefixed with "use"
- Return a cleanup function from useEffect for subscriptions and timers
Custom Hooks:
- Always prefix custom hooks with "use" (e.g.,
useOnlineStatus)
- Keep hooks focused on a single concern
- Return stable object references to prevent unnecessary re-renders
- Clean up all side effects (subscriptions, timers, async operations)
Example implementation:
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
State Management
Core Principles:
- Derive state, don't sync it - Calculate values during render instead of managing multiple related state variables
- Implement single sources of truth to prevent synchronization bugs
- Model async state with explicit loading and error states
- Use React Context for shared state across component trees
When to use useReducer vs useState:
Use useReducer when:
- State has multiple sub-values that change together
- Next state depends on previous state values
- Complex state transitions with multiple actions
- State logic becomes difficult to test inline
- Calling multiple setState functions in sequence
Use useState when:
- Simple primitive values (boolean, string, number)
- Independent state variables
- Straightforward state updates without complex logic
useReducer Example:
const initialFormState = {
data: { name: '', email: '', company: '' },
loading: false,
errors: {},
submitted: false
};
function formReducer(state, action) {
switch (action.type) {
case 'FIELD_CHANGE':
return {
...state,
data: { ...state.data, [action.field]: action.value },
errors: { ...state.errors, [action.field]: null }
};
case 'SUBMIT_START':
return { ...state, loading: true, errors: {}, submitted: false };
case 'SUBMIT_SUCCESS':
return { ...state, loading: false, submitted: true };
case 'SUBMIT_ERROR':
return { ...state, loading: false, errors: action.errors };
case 'RESET':
return initialFormState;
default:
return state;
}
}
State Derivation Example:
function GameBoard() {
const [squares, setSquares] = useState(Array(9).fill(null));
const winner = calculateWinner(squares);
const nextPlayer = calculateNextPlayer(squares);
const status = calculateStatus(winner, squares, nextPlayer);
return <div>{status}</div>;
}
function GameBoard() {
const [squares, setSquares] = useState(Array(9).fill(null));
const [winner, setWinner] = useState(null);
const [nextPlayer, setNextPlayer] = useState('X');
const [status, setStatus] = useState('Next player: X');
useEffect(() => {
const newWinner = calculateWinner(squares);
const newNextPlayer = calculateNextPlayer(squares);
const newStatus = calculateStatus(newWinner, squares, newNextPlayer);
setWinner(newWinner);
setNextPlayer(newNextPlayer);
setStatus(newStatus);
}, [squares]);
}
TypeScript & Type Safety
Strict Typing:
- Use strict typing, avoid
any type
- Define interfaces for object shapes and component props
- Use Zod schemas for runtime validation and type inference
- Always use
z.infer<typeof schema> to derive types from Zod schemas
- Wrap feature subtrees in error boundaries that render a fallback UI
Performance Optimization
Optimization Strategy:
- Implement code splitting and lazy loading for routes and components
- Use virtualization (react-window, react-virtuoso) for large lists
- Serve images at their displayed size and lazy-load below-the-fold images
- Minimize bundle size through tree shaking and dead code elimination
- Profile performance with React DevTools and browser tools
Memoization Guidelines:
- Use
useMemo ONLY for expensive computations - measure first!
- Use
useCallback for callback functions passed to memoized child components
- Use
React.memo for expensive components to avoid unnecessary re-renders
Example:
function ExpensiveComponent() {
const [items, setItems] = useState([]);
const [filter, setFilter] = useState('');
const filteredAndSortedItems = useMemo(() => {
return items
.filter(item => item.name.includes(filter))
.sort((a, b) => expensiveComparisonFunction(a, b));
}, [items, filter]);
return <ItemList items={filteredAndSortedItems} />;
}
React SOLID Principles
Single Responsibility:
Separate data fetching logic from UI rendering.
function useUserData(fetchUser) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser().then(setUser);
}, [fetchUser]);
return user;
}
function UserCard({ fetchUser }) {
const user = useUserData(fetchUser);
if (!user) return <p>Loading...</p>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
Open/Closed Principle:
Extend behavior via composition, not modification.
function Alert({ className, children }) {
return <div className={className}>{children}</div>;
}
const SuccessAlert = ({ message }) => (
<Alert className="alert-success">{message}</Alert>
);
const ErrorAlert = ({ message }) => (
<Alert className="alert-error">{message}</Alert>
);
TanStack Query Best Practices
Optimistic Updates:
const mutation = useMutation({
mutationFn: createItem,
onMutate: async newItem => {
await queryClient.cancelQueries({ queryKey: ['items'] });
const previousItems = queryClient.getQueryData(['items']);
queryClient.setQueryData(['items'], old => [...old, newItem]);
return { previousItems };
},
onError: (err, newItem, context) => {
queryClient.setQueryData(['items'], context.previousItems);
},
onSettled: () => {
queryClient.invalidateQueries(['items']);
},
});
Accessibility Standards
Essential Requirements:
- Use semantic HTML elements (button, nav, main, section)
- Add ARIA labels and roles to non-semantic interactive elements
- Support keyboard navigation; manage focus on route changes and modal open/close
- Maintain color contrast ratios meeting WCAG AA standards
- Test with screen readers and accessibility tools
Code Organization
Import and File Structure:
- Follow ordered imports: builtin → external → internal → parent → sibling
- Use absolute imports for cleaner dependency management
- Organize by feature domains with clear boundaries
- Extract conditional logic into well-named variables
- Group related functionality in cohesive modules
Implementation Workflow
When applying this skill to frontend development tasks:
-
Analyze Requirements: Understand the specific frontend task - component creation, bug fixing, performance optimization, or accessibility improvements
-
Review Existing Code: Use Read/Grep to examine current implementation and identify patterns or issues
-
Implement: Apply the patterns from the sections above (functional components, TypeScript strict mode, the listed performance and accessibility rules)
Common Anti-Patterns to Avoid
State Management:
- ❌ Using multiple useState calls for interdependent state
- ❌ Syncing state manually with useEffect
- ❌ Creating derived state instead of computing during render
Performance:
- ❌ Premature optimization with useMemo before measuring
- ❌ Using useCallback without React.memo on child components
- ❌ Deeply nested component trees without lazy loading
TypeScript:
- ❌ Using
any type to bypass type checking
- ❌ Missing prop interfaces for components
- ❌ Ignoring type errors with
@ts-ignore
Accessibility:
- ❌ Using div instead of button for clickable elements
- ❌ Missing alt text on images
- ❌ Insufficient color contrast
- ❌ No keyboard navigation support