Skip to main content

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

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

React 18+ patterns with TypeScript, hooks, state management, and component architecture for production applications.

Core Capabilities

  1. React 18 Concurrent Features - Suspense, transitions, streaming
  2. TypeScript Strict Mode - Type-safe components and hooks
  3. Custom Hooks - Composable, reusable logic
  4. State Management - Zustand, Jotai, TanStack Query
  5. 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-PatternProblemSolution
Props drilling 5+ levelsMaintenance nightmareUse Context or state management
No memoization for expensive computationsPerformance issuesUse useMemo for heavy calculations
useEffect for derived stateUnnecessary complexityUse useMemo instead
Mutating state directlyReact doesn't detect changesUse immer or immutable updates
Not cleaning up effectsMemory leaksReturn cleanup function from useEffect
Inline function propsCauses child re-rendersUse useCallback
Large component files (>500 lines)Hard to maintainSplit into smaller components
No error boundariesApp crashes on errorsWrap components with ErrorBoundary
Fetching in useEffectRace conditionsUse TanStack Query instead
Global state for everythingOver-engineeringUse 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