AgentStack
SKILL verified MIT Self-run

React Patterns

skill-everyone-needs-a-copilot-claude-copilot-react-patterns · by Everyone-Needs-A-Copilot

>-

No reviews yet
0 installs
14 views
0.0% view→install

Install

$ agentstack add skill-everyone-needs-a-copilot-claude-copilot-react-patterns

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of React Patterns? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

React Patterns

Modern React patterns, hooks best practices, anti-patterns, and quality rules.

Core Principles

| Principle | Description | |-----------|-------------| | Composition | Small, focused components over inheritance | | Unidirectional | Data flows down, events flow up | | Declarative | Describe what, not how | | Hooks | Functional components with hooks over class components |

Component Patterns

Functional Components

// GOOD: Typed functional component
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

export function Button({
  label,
  onClick,
  variant = 'primary',
  disabled = false,
}: ButtonProps) {
  return (
    
      {label}
    
  );
}

// BAD: Class component (outdated)
class Button extends React.Component { }

Composition Over Props Drilling

// GOOD: Compound components
function Card({ children }: { children: React.ReactNode }) {
  return {children};
}

Card.Header = function CardHeader({ children }: { children: React.ReactNode }) {
  return {children};
};

Card.Body = function CardBody({ children }: { children: React.ReactNode }) {
  return {children};
};

// Usage

  Title
  Content

Render Props / Children as Function

// GOOD: Flexible render pattern
interface DataFetcherProps {
  url: string;
  children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode;
}

function DataFetcher({ url, children }: DataFetcherProps) {
  const { data, loading, error } = useFetch(url);
  return <>{children(data, loading, error)};
}

// Usage
 url="/api/users">
  {(users, loading, error) => {
    if (loading) return ;
    if (error) return ;
    return ;
  }}

Hooks Best Practices

useState

// GOOD: Typed state
const [user, setUser] = useState(null);
const [items, setItems] = useState([]);

// GOOD: Functional updates for derived state
setCount(prev => prev + 1);
setItems(prev => [...prev, newItem]);

// BAD: Object mutation
setUser({ ...user, name: 'New' });  // Works but...
user.name = 'New';  // NEVER mutate directly!
setUser(user);

useEffect

// GOOD: Proper dependency array
useEffect(() => {
  const controller = new AbortController();

  async function fetchData() {
    try {
      const response = await fetch(url, { signal: controller.signal });
      setData(await response.json());
    } catch (error) {
      if (!controller.signal.aborted) {
        setError(error as Error);
      }
    }
  }

  fetchData();
  return () => controller.abort();
}, [url]);  // Re-run when url changes

// BAD: Missing dependencies
useEffect(() => {
  fetchData(userId);  // userId not in deps - stale closure!
}, []);

// BAD: Object/array in deps (always new reference)
useEffect(() => {
  doSomething(options);
}, [options]);  // Infinite loop if options = {} inline

useMemo / useCallback

// GOOD: Expensive computation
const sortedItems = useMemo(
  () => items.sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

// GOOD: Stable callback for child props
const handleClick = useCallback(
  (id: string) => {
    onSelect(id);
  },
  [onSelect]
);

// BAD: Premature optimization
const double = useMemo(() => value * 2, [value]);  // Too simple

// BAD: No deps for callback
const handleClick = useCallback(() => {
  onSelect(selectedId);  // selectedId is stale!
}, []);

Custom Hooks

// GOOD: Extract reusable logic
function useLocalStorage(key: string, initialValue: T) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

// Usage
const [theme, setTheme] = useLocalStorage('theme', 'light');

Anti-Patterns to Avoid

Index as Key

// BAD: Index key causes render issues
{items.map((item, index) => (
    // Re-renders all on reorder!
))}

// GOOD: Stable unique key
{items.map(item => (
  
))}

Props in State

// BAD: Copying props to state
function UserProfile({ user }: { user: User }) {
  const [userData, setUserData] = useState(user);  // Stale!
  // ...
}

// GOOD: Derive from props or use effect to sync
function UserProfile({ user }: { user: User }) {
  const displayName = user.name.toUpperCase();  // Derive directly
  // ...
}

useEffect for Transforms

// BAD: Effect for derived data
const [items, setItems] = useState([]);
const [filtered, setFiltered] = useState([]);

useEffect(() => {
  setFiltered(items.filter(i => i.active));
}, [items]);

// GOOD: Compute directly or useMemo
const filtered = useMemo(
  () => items.filter(i => i.active),
  [items]
);

Excessive Re-renders

// BAD: New object/function on every render
 handleClick(id)}  // New function every render!
/>

// GOOD: Stable references
const config = useMemo(() => ({ theme: 'dark' }), []);
const handleChildClick = useCallback(() => handleClick(id), [id]);

State Management

Context for Global State

// GOOD: Typed context with provider
interface AuthContextType {
  user: User | null;
  login: (credentials: Credentials) => Promise;
  logout: () => void;
}

const AuthContext = createContext(null);

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
}

Reducer for Complex State

type Action =
  | { type: 'SET_LOADING' }
  | { type: 'SET_DATA'; payload: Data }
  | { type: 'SET_ERROR'; payload: Error };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'SET_LOADING':
      return { ...state, loading: true, error: null };
    case 'SET_DATA':
      return { ...state, loading: false, data: action.payload };
    case 'SET_ERROR':
      return { ...state, loading: false, error: action.payload };
  }
}

Quality Checklist

| Check | Rule | |-------|------| | Unique keys | Never use index as key for dynamic lists | | Hook deps | All dependencies listed, no lint suppressions | | No props in state | Derive from props directly | | Stable callbacks | useCallback for event handlers passed to children | | Error boundaries | Wrap feature sections | | Typed props | Interface for all component props | | Controlled inputs | value + onChange, not defaultValue | | Cleanup effects | Return cleanup function for subscriptions | | Memoize expensive | useMemo for costly computations only | | Custom hooks | Extract reusable stateful logic |

Invocation — React Anti-Pattern Checker (L3 Script)

Run the checker on any JSX or TSX file. Consume its output only — the script source never enters context.

Scope note: This is a regex-based structural checker, not a full JSX/AST parser. It reliably catches three closed-set patterns:

  1. key={index} (index used as key in map)
  2. Missing key prop in .map( JSX render (JSX element without key= on same or following lines)
  3. Hooks called inside conditional blocks (if/&&/ternary body)

It does NOT detect prop-drilling depth (requires component tree analysis), missing useEffect dependencies (requires full scope analysis), or render performance issues. Use eslint-plugin-react-hooks for those.

Run via Bash (file argument):

python .claude/skills/code/react-patterns/scripts/react_patterns.py path/to/Component.tsx

Run via Bash (stdin — paste code or pipe):

cat path/to/Component.jsx | python .claude/skills/code/react-patterns/scripts/react_patterns.py -

The script outputs:

  1. A JSON object with findings (list of anti-patterns with rule, severity, line, message) and a summary of counts by severity.
  2. A human-readable markdown table sorted by severity descending.

Detected rules:

  • INDEX_AS_KEY HIGH — key={index} or key={i} in map callback (causes incorrect reconciliation on reorder)
  • MISSING_KEY MEDIUM — .map( call with JSX element that has no key= prop within 5 lines
  • HOOK_IN_CONDITIONAL HIGH — Hook call (use...()) inside an if statement or && expression (violates Rules of Hooks)

Error handling: Script exits 1 on unreadable file. Exits 0 even if findings are present.

What the agent does with the output:

  1. HIGH findings (INDEXASKEY, HOOKINCONDITIONAL) must be fixed before merge.
  2. MISSING_KEY findings may be false positives for static lists — verify that the list is truly static before dismissing.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.