Agent Skills Framework Extension
Frontend React Patterns Skill
When to Use This Skill
Use this skill when implementing frontend react patterns patterns in your codebase.
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- Follow the best practices outlined in this skill
React 18+ patterns with TypeScript, hooks, state management, and component architecture for production applications.
Core Capabilities
- React 18 Concurrent Features - Suspense, transitions, streaming
- TypeScript Strict Mode - Type-safe components and hooks
- Custom Hooks - Composable, reusable logic
- State Management - Zustand, Jotai, TanStack Query
- Performance - Memoization, virtualization, lazy loading
Component Architecture
Compound Component Pattern
// src/components/Menu/Menu.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
interface MenuContextType {
isOpen: boolean;
toggle: () => void;
close: () => void;
}
const MenuContext = createContext<MenuContextType | null>(null);
function useMenuContext() {
const context = useContext(MenuContext);
if (!context) {
throw new Error('Menu components must be used within Menu.Root');
}
return context;
}
interface RootProps {
children: ReactNode;
defaultOpen?: boolean;
}
function Root({ children, defaultOpen = false }: RootProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
const toggle = () => setIsOpen(prev => !prev);
const close = () => setIsOpen(false);
return (
<MenuContext.Provider value={{ isOpen, toggle, close }}>
<div className="relative">{children}</div>
</MenuContext.Provider>
);
}
function Trigger({ children }: { children: ReactNode }) {
const { toggle } = useMenuContext();
return (
<button onClick={toggle} className="menu-trigger">
{children}
</button>
);
}
function Content({ children }: { children: ReactNode }) {
const { isOpen } = useMenuContext();
if (!isOpen) return null;
return (
<div className="absolute top-full left-0 mt-1 bg-white shadow-lg rounded-md">
{children}
</div>
);
}
function Item({ children, onClick }: { children: ReactNode; onClick?: () => void }) {
const { close } = useMenuContext();
const handleClick = () => {
onClick?.();
close();
};
return (
<button onClick={handleClick} className="menu-item w-full text-left px-4 py-2 hover:bg-gray-100">
{children}
</button>
);
}
export const Menu = { Root, Trigger, Content, Item };
// Usage:
// <Menu.Root>
// <Menu.Trigger>Options</Menu.Trigger>
// <Menu.Content>
// <Menu.Item onClick={handleEdit}>Edit</Menu.Item>
// <Menu.Item onClick={handleDelete}>Delete</Menu.Item>
// </Menu.Content>
// </Menu.Root>
Custom Hooks
// src/hooks/useAsync.ts
import { useState, useCallback, useEffect } from 'react';
interface AsyncState<T> {
data: T | null;
error: Error | null;
isLoading: boolean;
isError: boolean;
isSuccess: boolean;
}
interface UseAsyncOptions<T> {
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
immediate?: boolean;
}
export function useAsync<T, Args extends unknown[] = []>(
asyncFn: (...args: Args) => Promise<T>,
options: UseAsyncOptions<T> = {}
) {
const { onSuccess, onError, immediate = false } = options;
const [state, setState] = useState<AsyncState<T>>({
data: null,
error: null,
isLoading: false,
isError: false,
isSuccess: false,
});
const execute = useCallback(
async (...args: Args) => {
setState(prev => ({ ...prev, isLoading: true, isError: false }));
try {
const data = await asyncFn(...args);
setState({ data, error: null, isLoading: false, isError: false, isSuccess: true });
onSuccess?.(data);
return data;
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
setState({ data: null, error: err, isLoading: false, isError: true, isSuccess: false });
onError?.(err);
throw err;
}
},
[asyncFn, onSuccess, onError]
);
const reset = useCallback(() => {
setState({
data: null,
error: null,
isLoading: false,
isError: false,
isSuccess: false,
});
}, []);
useEffect(() => {
if (immediate) {
execute(...([] as unknown as Args));
}
}, [immediate, execute]);
return { ...state, execute, reset };
}
// src/hooks/useDebounce.ts
import { useState, useEffect } from 'react';
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// src/hooks/useLocalStorage.ts
import { useState, useCallback } from 'react';
export function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((prev: T) => T)) => void, () => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
if (typeof window === 'undefined') return initialValue;
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setValue = useCallback(
(value: T | ((prev: T) => T)) => {
setStoredValue(prev => {
const valueToStore = value instanceof Function ? value(prev) : value;
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
return valueToStore;
});
},
[key]
);
const removeValue = useCallback(() => {
if (typeof window !== 'undefined') {
window.localStorage.removeItem(key);
}
setStoredValue(initialValue);
}, [key, initialValue]);
return [storedValue, setValue, removeValue];
}
State Management (Zustand)
// src/store/userStore.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
interface UserState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
}
interface UserActions {
login: (email: string, password: string) => Promise<void>;
logout: () => void;
updateProfile: (updates: Partial<User>) => void;
clearError: () => void;
}
type UserStore = UserState & UserActions;
export const useUserStore = create<UserStore>()(
devtools(
persist(
immer((set, get) => ({
// State
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
// Actions
login: async (email: string, password: string) => {
set(state => {
state.isLoading = true;
state.error = null;
});
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Invalid credentials');
}
const user = await response.json();
set(state => {
state.user = user;
state.isAuthenticated = true;
state.isLoading = false;
});
} catch (error) {
set(state => {
state.error = error instanceof Error ? error.message : 'Login failed';
state.isLoading = false;
});
}
},
logout: () => {
set(state => {
state.user = null;
state.isAuthenticated = false;
});
},
updateProfile: (updates: Partial<User>) => {
set(state => {
if (state.user) {
Object.assign(state.user, updates);
}
});
},
clearError: () => {
set(state => {
state.error = null;
});
},
})),
{
name: 'user-storage',
partialize: state => ({ user: state.user, isAuthenticated: state.isAuthenticated }),
}
),
{ name: 'UserStore' }
)
);
// Selectors for optimization
export const selectUser = (state: UserStore) => state.user;
export const selectIsAuthenticated = (state: UserStore) => state.isAuthenticated;
export const selectIsAdmin = (state: UserStore) => state.user?.role === 'admin';
Data Fetching (TanStack Query)
// src/api/users.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
interface User {
id: string;
name: string;
email: string;
}
// Query keys factory
export const userKeys = {
all: ['users'] as const,
lists: () => [...userKeys.all, 'list'] as const,
list: (filters: Record<string, unknown>) => [...userKeys.lists(), filters] as const,
details: () => [...userKeys.all, 'detail'] as const,
detail: (id: string) => [...userKeys.details(), id] as const,
};
// API functions
async function fetchUsers(): Promise<User[]> {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Failed to fetch users');
return response.json();
}
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
}
async function createUser(data: Omit<User, 'id'>): Promise<User> {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error('Failed to create user');
return response.json();
}
async function updateUser({ id, ...data }: Partial<User> & { id: string }): Promise<User> {
const response = await fetch(`/api/users/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error('Failed to update user');
return response.json();
}
// Hooks
export function useUsers() {
return useQuery({
queryKey: userKeys.lists(),
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
export function useUser(id: string) {
return useQuery({
queryKey: userKeys.detail(id),
queryFn: () => fetchUser(id),
enabled: !!id,
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
},
});
}
export function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateUser,
onSuccess: (data) => {
queryClient.setQueryData(userKeys.detail(data.id), data);
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
},
});
}
Performance Optimization
// src/components/VirtualList.tsx
import { useRef, useState, useCallback, useMemo } from 'react';
interface VirtualListProps<T> {
items: T[];
itemHeight: number;
containerHeight: number;
renderItem: (item: T, index: number) => React.ReactNode;
overscan?: number;
}
export function VirtualList<T>({
items,
itemHeight,
containerHeight,
renderItem,
overscan = 3,
}: VirtualListProps<T>) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);
const handleScroll = useCallback(() => {
if (containerRef.current) {
setScrollTop(containerRef.current.scrollTop);
}
}, []);
const { startIndex, endIndex, offsetY } = useMemo(() => {
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
const visibleCount = Math.ceil(containerHeight / itemHeight);
const endIndex = Math.min(items.length - 1, startIndex + visibleCount + overscan * 2);
const offsetY = startIndex * itemHeight;
return { startIndex, endIndex, offsetY };
}, [scrollTop, itemHeight, containerHeight, items.length, overscan]);
const visibleItems = useMemo(() => {
return items.slice(startIndex, endIndex + 1);
}, [items, startIndex, endIndex]);
const totalHeight = items.length * itemHeight;
return (
<div
ref={containerRef}
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={handleScroll}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) => (
<div key={startIndex + index} style={{ height: itemHeight }}>
{renderItem(item, startIndex + index)}
</div>
))}
</div>
</div>
</div>
);
}
// Memoization utilities
import { memo, useMemo, useCallback } from 'react';
interface ExpensiveComponentProps {
data: Record<string, unknown>[];
onItemClick: (id: string) => void;
}
export const ExpensiveComponent = memo(function ExpensiveComponent({
data,
onItemClick,
}: ExpensiveComponentProps) {
const processedData = useMemo(() => {
// Expensive computation
return data.map(item => ({
...item,
computed: Object.keys(item).length,
}));
}, [data]);
const handleClick = useCallback(
(id: string) => {
onItemClick(id);
},
[onItemClick]
);
return (
<ul>
{processedData.map(item => (
<li key={item.id as string} onClick={() => handleClick(item.id as string)}>
{JSON.stringify(item)}
</li>
))}
</ul>
);
});
Usage Examples
Apply React Patterns to New Feature
Apply frontend-react-patterns skill to implement a dashboard with virtual scrolling, state management, and real-time updates
Create Component Library
Apply frontend-react-patterns skill to design compound components for a form builder with validation
Optimize Performance
Apply frontend-react-patterns skill to profile and optimize a slow-rendering list component
Integration Points
- testing-strategies - Component testing patterns
- websocket-realtime - Real-time state synchronization
- performance-profiling - React DevTools integration
Success Output
When successful, this skill MUST output:
✅ SKILL COMPLETE: frontend-react-patterns
Completed:
- [x] Component architecture established
- [x] Custom hooks implemented
- [x] State management configured
- [x] Performance optimization applied
- [x] TypeScript types defined
Outputs:
- Components: src/components/{ComponentName}/
- Hooks: src/hooks/use{HookName}.ts
- Store: src/store/{storeName}Store.ts
- Types: src/types/{domain}.ts
Implementation Summary:
- Components created: {count}
- Custom hooks: {count}
- State stores: {count}
- Compound components: {count}
- Performance: Memoization applied to {count} components
Completion Checklist
Before marking this skill as complete, verify:
- All components TypeScript strict mode compliant
- Compound components properly scoped (Root, Trigger, Content pattern)
- Custom hooks have proper dependency arrays
- State management configured (Zustand/Jotai/TanStack Query)
- Performance optimization: useMemo, useCallback, memo applied
- Virtual scrolling implemented for large lists
- Query keys factory created (TanStack Query)
- Optimistic updates configured for mutations
- Error boundaries implemented
- Loading states handled
- Accessibility attributes added (ARIA)
- Component props properly typed
Failure Indicators
This skill has FAILED if:
- ❌ TypeScript errors present
- ❌ Components re-render excessively (no memoization)
- ❌ Props drilling through multiple levels (use context/state management)
- ❌ Hooks violate Rules of Hooks
- ❌ State updates cause infinite loops
- ❌ No error boundaries (app crashes on errors)
- ❌ No loading states (poor UX)
- ❌ Accessibility issues (missing ARIA labels)
When NOT to Use
Do NOT use this skill when:
- Building a simple static site (use plain HTML/CSS or simple templating)
- Need server-side rendering only (use Next.js patterns instead)
- Working with Vue/Angular/Svelte (use framework-specific patterns)
- Building a mobile app (use React Native patterns instead)
- Prototyping quickly (these patterns add complexity for speed)
- Team unfamiliar with advanced React (start with simpler patterns)
- Working with React <18 (concurrent features won't work)
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Props drilling 5+ levels | Maintenance nightmare | Use Context or state management |
| No memoization for expensive computations | Performance issues | Use useMemo for heavy calculations |
| useEffect for derived state | Unnecessary complexity | Use useMemo instead |
| Mutating state directly | React doesn't detect changes | Use immer or immutable updates |
| Not cleaning up effects | Memory leaks | Return cleanup function from useEffect |
| Inline function props | Causes child re-renders | Use useCallback |
| Large component files (>500 lines) | Hard to maintain | Split into smaller components |
| No error boundaries | App crashes on errors | Wrap components with ErrorBoundary |
| Fetching in useEffect | Race conditions | Use TanStack Query instead |
| Global state for everything | Over-engineering | Use local state when possible |
Principles
This skill embodies:
- #1 Full Automation - Automated state management with TanStack Query
- #3 Keep It Simple - Compound components provide simple, composable APIs
- #4 Separation of Concerns - Custom hooks separate logic from presentation
- #5 Eliminate Ambiguity - TypeScript strict mode enforces type safety
- #6 Clear, Understandable, Explainable - Explicit state transitions and selectors
- #13 Measure, Don't Guess - React DevTools profiling for performance optimization
Full Standard: CODITECT-STANDARD-AUTOMATION.md