home / skills / williamzujkowski / standards / react

react skill

/skills/frontend/react

This skill helps you enforce React frontend standards across hooks, state management, performance, testing, and accessibility for modern SPAs.

npx playbooks add skill williamzujkowski/standards --skill react

Review the files below or copy the command above to add this skill to your agents.

Files (11)
SKILL.md
9.9 KB
---
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

```tsx
// 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

```bash
# 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

```tsx
// Lazy initialization for expensive initial state
const [data, setData] = useState(() => computeExpensiveValue());

// Functional updates when new state depends on old
setCount(prevCount => prevCount + 1);
```

### useEffect

```tsx
// 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

```tsx
// 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

```tsx
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

```tsx
// 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)

```tsx
// Split contexts to reduce re-renders
<AuthProvider>
  <ThemeProvider>
    <LocaleProvider>
      <App />
    </LocaleProvider>
  </ThemeProvider>
</AuthProvider>
```

### React Query (Server State)

```tsx
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)

```tsx
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

```tsx
const ExpensiveComponent = React.memo(({ data }: Props) => {
  return <div>{/* Expensive render */}</div>;
});
```

### Code Splitting

```tsx
const Home = lazy(() => import('./pages/Home'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/" element={<Home />} />
      </Routes>
    </Suspense>
  );
}
```

### Virtual Scrolling

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
<nav><ul><li><a href="/">Home</a></li></ul></nav>
<main><article><h1>Title</h1><section><h2>Section</h2></section></article></main>
<footer>&copy; 2024 Company</footer>
```

### ARIA and Keyboard Navigation

```tsx
<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

```tsx
<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

```tsx
// 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

```tsx
// Prefer httpOnly cookies (backend sets it)
// If localStorage required: short expiry, refresh tokens, clear on logout
```

---

## Summary

1. **Hooks mastery** for state and side effects
2. **Appropriate state management** (local -> Context -> external library)
3. **Performance optimization** with memoization and code splitting
4. **User-centric testing** with React Testing Library
5. **Accessibility** (WCAG 2.1 AA) with semantic HTML and ARIA
6. **Security** through XSS prevention and secure auth patterns

---

## Resources

See [REFERENCE.md](./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

Overview

This skill codifies battle-tested React frontend standards for building modern single-page applications. It focuses on hooks (useState, useEffect, useContext, custom hooks), state management choices (Context API, React Query, Redux, Zustand), performance (memoization, lazy loading, code splitting), testing with React Testing Library, and WCAG 2.1 accessibility practices. The goal is a practical checklist and patterns you can apply immediately to new or existing projects.

How this skill works

The skill inspects common frontend concerns and prescribes concrete patterns: when to use local state vs global state, safe effect and cleanup patterns, custom hook conventions, and component composition patterns. It also enforces performance tactics (React.memo, useMemo, code splitting with React.lazy, virtual scrolling) and testing strategies focused on user interactions using React Testing Library. Accessibility and security checks (semantic HTML, ARIA, XSS mitigation) are included as mandatory touchpoints.

When to use it

  • Starting a new React SPA and establishing consistent standards.
  • Deciding between local state, Context, React Query, or a global store.
  • Optimizing rendering or reducing bundle size in production builds.
  • Writing user-centric tests and raising coverage for components and hooks.
  • Auditing accessibility for WCAG 2.1 AA compliance.

Best practices

  • Prefer functional components and hooks; keep components small (<300 lines).
  • Follow the state-management decision tree: local -> Context -> React Query -> Redux/Zustand.
  • Avoid recreating objects/arrays inline in JSX; memoize with useMemo/useCallback where needed.
  • Code-split routes/components with React.lazy and Suspense; lazy-load images and long lists.
  • Test via user flows with React Testing Library; target high coverage for hooks and utils.
  • Use semantic HTML and ARIA roles; validate keyboard navigation and screen-reader announcements.

Example use cases

  • Implementing theme and auth using separate Context providers to minimize re-renders.
  • Switching server-state handling to React Query for caching and automatic refetching.
  • Converting a heavy dashboard widget to React.memo and virtualized list to cut render time.
  • Creating a reusable useFetch custom hook with proper mount-safe cleanup.
  • Writing end-to-end component tests that simulate user typing and submission with userEvent.

FAQ

When should I use Redux over Context API?

Use Redux (or Zustand) when state is complex, has many actions, or requires predictable structure and tooling; use Context + useReducer for simpler cross-component UI state.

How do I prevent memory leaks in async effects?

Use an isMounted flag or AbortController inside useEffect and clean up subscriptions in the return function to avoid setState on unmounted components.