react-frontend
React frontend standards covering hooks (useState, useEffect, useContext, custom hooks), state management (Context API, Redux, Zustand), performance optimization (memoization, lazy loading, code splitting), testing with React Testing Library, and accessibility (WCAG 2.1, ARIA) for modern SPAs
$ Installer
git clone https://github.com/williamzujkowski/standards /tmp/standards && cp -r /tmp/standards/skills/frontend/react ~/.claude/skills/standards// tip: Run this command in your terminal to install the skill
SKILL.md
name: react-frontend description: React frontend standards covering hooks (useState, useEffect, useContext, custom hooks), state management (Context API, Redux, Zustand), performance optimization (memoization, lazy loading, code splitting), testing with React Testing Library, and accessibility (WCAG 2.1, ARIA) for modern SPAs
React Frontend Development Standards
Quick Reference
Essential Hooks
// State Hook
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
// Effect Hook
useEffect(() => {
// Side effect
return () => {/* cleanup */};
}, [dependencies]);
// Context Hook
const theme = useContext(ThemeContext);
// Ref Hook
const inputRef = useRef<HTMLInputElement>(null);
// Memo Hooks
const value = useMemo(() => expensiveCalc(a, b), [a, b]);
const callback = useCallback(() => doSomething(a), [a]);
State Management Decision Tree
Is state needed in >2 components?
NO -> Use local state (useState)
YES -> Is it global UI state (theme, auth)?
YES -> Use Context API
NO -> Is it server data (API responses)?
YES -> Use React Query/TanStack Query
NO -> Complex state with many actions?
YES -> Use Redux Toolkit or Zustand
NO -> Use Context API + useReducer
Component Checklist
- Use functional components with hooks
- Destructure props with TypeScript types
- Use semantic HTML elements
- Add proper ARIA labels where needed
- Memoize expensive computations with useMemo
- Memoize callbacks passed to children with useCallback
- Add loading and error states
- Clean up effects with return function
- Keep components under 300 lines
Performance Checklist
- Use React.lazy() for code splitting
- Wrap expensive components in React.memo()
- Implement virtual scrolling for long lists
- Lazy load images with loading="lazy"
- Avoid inline object/array creation in JSX
Quick Start
# Create React app with TypeScript
npx create-react-app my-app --template typescript
# Install testing library
npm install --save-dev @testing-library/react @testing-library/jest-dom
# Install React Query for server state
npm install @tanstack/react-query
# State management (choose one)
npm install zustand # Lightweight
npm install @reduxjs/toolkit # Enterprise
Core Hooks Patterns
useState
// Lazy initialization for expensive initial state
const [data, setData] = useState(() => computeExpensiveValue());
// Functional updates when new state depends on old
setCount(prevCount => prevCount + 1);
useEffect
// Component mount (runs once)
useEffect(() => {
console.log('Component mounted');
}, []);
// Cleanup pattern for async operations
useEffect(() => {
let isMounted = true;
async function fetchData() {
const result = await api.getData();
if (isMounted) setData(result);
}
fetchData();
return () => { isMounted = false; };
}, []);
// Subscription cleanup
useEffect(() => {
const subscription = api.subscribe(userId);
return () => subscription.unsubscribe();
}, [userId]);
useContext
// Create context with TypeScript
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// Custom hook for consuming context (always use this pattern)
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
};
useReducer
type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return { count: 0 };
default: return state;
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
Custom Hooks
// Always prefix with 'use'
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let isMounted = true;
fetch(url)
.then(res => res.json())
.then(data => { if (isMounted) { setData(data); setLoading(false); }})
.catch(err => { if (isMounted) { setError(err); setLoading(false); }});
return () => { isMounted = false; };
}, [url]);
return { data, loading, error };
}
State Management
Context API (Theme, Auth, Locale)
// Split contexts to reduce re-renders
<AuthProvider>
<ThemeProvider>
<LocaleProvider>
<App />
</LocaleProvider>
</ThemeProvider>
</AuthProvider>
React Query (Server State)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function Users() {
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createUser,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
});
}
Zustand (Lightweight Global State)
import create from 'zustand';
const useBearStore = create<BearState>((set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
}));
// Usage (no Provider needed!)
const bears = useBearStore((state) => state.bears);
Performance Optimization
React.memo
const ExpensiveComponent = React.memo(({ data }: Props) => {
return <div>{/* Expensive render */}</div>;
});
Code Splitting
const Home = lazy(() => import('./pages/Home'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</Suspense>
);
}
Virtual Scrolling
import { FixedSizeList } from 'react-window';
<FixedSizeList height={600} itemCount={10000} itemSize={35} width="100%">
{({ index, style }) => <div style={style}>Row {index}</div>}
</FixedSizeList>
Component Patterns
Props with Defaults
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
}
export function Button({ variant = 'primary', size = 'md', children }: ButtonProps) {
return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
}
Compound Components
const TabsContext = createContext<TabsContextType | undefined>(undefined);
export function Tabs({ children }: { children: React.ReactNode }) {
const [activeTab, setActiveTab] = useState('tab1');
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
Tabs.Tab = function Tab({ id, children }: TabProps) {
const context = useContext(TabsContext);
return (
<button onClick={() => context?.setActiveTab(id)}>
{children}
</button>
);
};
Testing with React Testing Library
User-Centric Testing
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('user can submit form', async () => {
const user = userEvent.setup();
const onSubmit = jest.fn();
render(<ContactForm onSubmit={onSubmit} />);
const nameInput = screen.getByRole('textbox', { name: /name/i });
await user.type(nameInput, 'John Doe');
await user.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({ name: 'John Doe' });
});
});
Testing Hooks
import { renderHook, act } from '@testing-library/react';
test('useCounter increments', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
act(() => { result.current.increment(); });
expect(result.current.count).toBe(1);
});
Coverage Targets
- Components: 80%+
- Hooks: 90%+
- Utils: 95%+
Accessibility (WCAG 2.1 AA)
Semantic HTML
<nav><ul><li><a href="/">Home</a></li></ul></nav>
<main><article><h1>Title</h1><section><h2>Section</h2></section></article></main>
<footer>© 2024 Company</footer>
ARIA and Keyboard Navigation
<button aria-label="Close dialog" onClick={onClose}>X</button>
<div role="dialog" aria-labelledby="dialog-title" aria-modal="true">
<h2 id="dialog-title">Confirm Action</h2>
</div>
<div role="alert" aria-live="assertive">{error}</div>
Form Accessibility
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
aria-invalid={!!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
{errors.email && <span id="email-error" role="alert">{errors.email}</span>}
Security
XSS Prevention
// React escapes by default - safe
<div>{userInput}</div>
// If dangerouslySetInnerHTML needed, use DOMPurify
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirtyHTML);
Secure Token Storage
// Prefer httpOnly cookies (backend sets it)
// If localStorage required: short expiry, refresh tokens, clear on logout
Summary
- Hooks mastery for state and side effects
- Appropriate state management (local -> Context -> external library)
- Performance optimization with memoization and code splitting
- User-centric testing with React Testing Library
- Accessibility (WCAG 2.1 AA) with semantic HTML and ARIA
- Security through XSS prevention and secure auth patterns
Resources
See REFERENCE.md for:
- Complete component templates
- Advanced hooks patterns (useReducer, usePrevious)
- Redux Toolkit implementation
- Compound component examples
- Comprehensive testing patterns
- Full accessibility checklists
- Production optimization examples
Repository

williamzujkowski
Author
williamzujkowski/standards/skills/frontend/react
11
Stars
0
Forks
Updated1w ago
Added1w ago