Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
josiahsiegel avatar

React Hooks Complete

  • 55 installs
  • 50 repo stars
  • Updated June 18, 2026
  • josiahsiegel/claude-plugin-marketplace

Helps with frontend development tasks.

About

react-hooks-complete is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted development.

  • react-hooks-complete
  • Frontend Development
  • AI-coding skill

React Hooks Complete by the numbers

  • 55 all-time installs (skills.sh)
  • +4 installs in the week ending Aug 2, 2026 (Skillselion tracking)
  • Ranked #1,259 of 2,245 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/josiahsiegel/claude-plugin-marketplace --skill react-hooks-complete

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs55
repo stars50
Last updatedJune 18, 2026
Repositoryjosiahsiegel/claude-plugin-marketplace

What it does

Helps with frontend development tasks.

Files

SKILL.mdMarkdownGitHub ↗

Quick Reference

HookPurposeExample
useStateLocal stateconst [count, setCount] = useState(0)
useReducerComplex stateconst [state, dispatch] = useReducer(reducer, init)
useEffectSide effectsuseEffect(() => { ... }, [deps])
useRefMutable ref / DOMconst ref = useRef<HTMLInputElement>(null)
useContextConsume contextconst value = useContext(MyContext)
useMemoMemoize valueconst computed = useMemo(() => calc(), [deps])
useCallbackStable functionconst fn = useCallback(() => {}, [deps])
React 19 HookPurpose
useTransitionNon-blocking updates
useDeferredValueDefer expensive renders
useActionStateServer Action state
useOptimisticOptimistic UI updates
useFormStatusForm pending state

When to Use This Skill

Use for React hooks implementation:

  • Choosing the right hook for your use case
  • Managing component state with useState/useReducer
  • Handling side effects and cleanup with useEffect
  • Building custom reusable hooks
  • Optimizing with useMemo and useCallback
  • Using React 19 concurrent features

For state management libraries: see react-state-management

---

React Hooks Complete Guide

State Hooks

useState

import { useState } from 'react';

// Basic usage
const [count, setCount] = useState(0);

// With initial function (lazy initialization)
const [state, setState] = useState(() => {
  const initialValue = expensiveComputation();
  return initialValue;
});

// Object state
const [user, setUser] = useState<User | null>(null);

// Updating object state (always create new reference)
setUser((prev) => prev ? { ...prev, name: 'New Name' } : null);

// Array state
const [items, setItems] = useState<Item[]>([]);

// Add item
setItems((prev) => [...prev, newItem]);

// Remove item
setItems((prev) => prev.filter((item) => item.id !== id));

// Update item
setItems((prev) =>
  prev.map((item) => (item.id === id ? { ...item, ...updates } : item))
);

useReducer

import { useReducer } from 'react';

// Define state and action types
interface State {
  count: number;
  error: string | null;
  loading: boolean;
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset'; payload: number }
  | { type: 'setError'; payload: string };

// Reducer function
function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'decrement':
      return { ...state, count: state.count - 1 };
    case 'reset':
      return { ...state, count: action.payload };
    case 'setError':
      return { ...state, error: action.payload };
    default:
      return state;
  }
}

// Usage
function Counter() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    error: null,
    loading: false,
  });

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset', payload: 0 })}>
        Reset
      </button>
    </div>
  );
}

useReducer with Immer

import { useReducer } from 'react';
import { produce } from 'immer';

interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

type Action =
  | { type: 'add'; text: string }
  | { type: 'toggle'; id: number }
  | { type: 'delete'; id: number };

function todosReducer(state: Todo[], action: Action): Todo[] {
  return produce(state, (draft) => {
    switch (action.type) {
      case 'add':
        draft.push({
          id: Date.now(),
          text: action.text,
          completed: false,
        });
        break;
      case 'toggle':
        const todo = draft.find((t) => t.id === action.id);
        if (todo) todo.completed = !todo.completed;
        break;
      case 'delete':
        const index = draft.findIndex((t) => t.id === action.id);
        if (index !== -1) draft.splice(index, 1);
        break;
    }
  });
}

Effect Hooks

useEffect

import { useEffect, useState } from 'react';

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;

    async function fetchUser() {
      setLoading(true);
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        if (!cancelled) {
          setUser(data);
        }
      } catch (error) {
        if (!cancelled) {
          console.error('Failed to fetch user:', error);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }

    fetchUser();

    // Cleanup function
    return () => {
      cancelled = true;
    };
  }, [userId]); // Re-run when userId changes

  if (loading) return <p>Loading...</p>;
  if (!user) return <p>User not found</p>;
  return <div>{user.name}</div>;
}

useLayoutEffect

import { useLayoutEffect, useRef, useState } from 'react';

function Tooltip({ text, children }: { text: string; children: ReactNode }) {
  const [tooltipHeight, setTooltipHeight] = useState(0);
  const tooltipRef = useRef<HTMLDivElement>(null);

  // Runs synchronously after DOM mutations but before paint
  useLayoutEffect(() => {
    if (tooltipRef.current) {
      setTooltipHeight(tooltipRef.current.getBoundingClientRect().height);
    }
  }, [text]);

  return (
    <div className="tooltip-container">
      {children}
      <div
        ref={tooltipRef}
        className="tooltip"
        style={{ top: -tooltipHeight - 10 }}
      >
        {text}
      </div>
    </div>
  );
}

useInsertionEffect (for CSS-in-JS libraries)

import { useInsertionEffect } from 'react';

// For CSS-in-JS library authors
function useCSS(rule: string) {
  useInsertionEffect(() => {
    const style = document.createElement('style');
    style.textContent = rule;
    document.head.appendChild(style);
    return () => {
      document.head.removeChild(style);
    };
  }, [rule]);
}

Context Hook

useContext

import { createContext, useContext, useState, ReactNode } from 'react';

// Define context type
interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: boolean;
}

// Create context with default value
const AuthContext = createContext<AuthContextType | null>(null);

// Provider component
export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const login = async (email: string, password: string) => {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    const userData = await response.json();
    setUser(userData);
  };

  const logout = () => {
    setUser(null);
  };

  return (
    <AuthContext.Provider
      value={{
        user,
        login,
        logout,
        isAuthenticated: !!user,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
}

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

// Usage
function LoginButton() {
  const { isAuthenticated, logout, user } = useAuth();

  if (isAuthenticated) {
    return (
      <div>
        <span>Welcome, {user?.name}</span>
        <button onClick={logout}>Logout</button>
      </div>
    );
  }

  return <Link href="/login">Login</Link>;
}

Ref Hooks

useRef

import { useRef, useEffect } from 'react';

// DOM reference
function TextInput() {
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} type="text" />;
}

// Mutable value (doesn't trigger re-render)
function Timer() {
  const intervalRef = useRef<NodeJS.Timeout | null>(null);
  const [count, setCount] = useState(0);

  const startTimer = () => {
    if (intervalRef.current) return;
    intervalRef.current = setInterval(() => {
      setCount((c) => c + 1);
    }, 1000);
  };

  const stopTimer = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  };

  useEffect(() => {
    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
      }
    };
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

useImperativeHandle

import { forwardRef, useImperativeHandle, useRef } from 'react';

interface VideoPlayerHandle {
  play: () => void;
  pause: () => void;
  seek: (time: number) => void;
}

const VideoPlayer = forwardRef<VideoPlayerHandle, { src: string }>(
  ({ src }, ref) => {
    const videoRef = useRef<HTMLVideoElement>(null);

    useImperativeHandle(ref, () => ({
      play() {
        videoRef.current?.play();
      },
      pause() {
        videoRef.current?.pause();
      },
      seek(time: number) {
        if (videoRef.current) {
          videoRef.current.currentTime = time;
        }
      },
    }));

    return <video ref={videoRef} src={src} />;
  }
);

// Usage
function App() {
  const playerRef = useRef<VideoPlayerHandle>(null);

  return (
    <div>
      <VideoPlayer ref={playerRef} src="/video.mp4" />
      <button onClick={() => playerRef.current?.play()}>Play</button>
      <button onClick={() => playerRef.current?.pause()}>Pause</button>
      <button onClick={() => playerRef.current?.seek(30)}>Skip to 30s</button>
    </div>
  );
}

Performance Hooks and React 19 Hooks

Detailed coverage of useMemo, useCallback, useTransition, useDeferredValue, and React 19-era hooks such as useActionState, useOptimistic, useFormStatus, and use lives in references/performance-and-react-19-hooks.md. Load that reference when optimizing renders, handling transitions/deferred values, or wiring React 19 forms and async resources.

Custom Hooks

useLocalStorage

import { useState, useEffect } from 'react';

function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    if (typeof window === 'undefined') return initialValue;

    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    try {
      localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error('Failed to save to localStorage:', error);
    }
  }, [key, value]);

  return [value, setValue] as const;
}

// Usage
function App() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle</button>;
}

useDebounce

import { useState, useEffect } from 'react';

function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debouncedValue;
}

// Usage
function SearchInput() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 300);

  useEffect(() => {
    if (debouncedQuery) {
      searchAPI(debouncedQuery);
    }
  }, [debouncedQuery]);

  return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}

useFetch

import { useState, useEffect } from 'react';

interface FetchState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

function useFetch<T>(url: string): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({
    data: null,
    loading: true,
    error: null,
  });

  useEffect(() => {
    let cancelled = false;

    const fetchData = async () => {
      setState({ data: null, loading: true, error: null });

      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        if (!cancelled) {
          setState({ data, loading: false, error: null });
        }
      } catch (error) {
        if (!cancelled) {
          setState({
            data: null,
            loading: false,
            error: error instanceof Error ? error : new Error('Unknown error'),
          });
        }
      }
    };

    fetchData();

    return () => {
      cancelled = true;
    };
  }, [url]);

  return state;
}

useMediaQuery

import { useState, useEffect } from 'react';

function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    const mediaQuery = window.matchMedia(query);
    setMatches(mediaQuery.matches);

    const handler = (event: MediaQueryListEvent) => {
      setMatches(event.matches);
    };

    mediaQuery.addEventListener('change', handler);
    return () => mediaQuery.removeEventListener('change', handler);
  }, [query]);

  return matches;
}

// Usage
function App() {
  const isMobile = useMediaQuery('(max-width: 768px)');
  const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');

  return (
    <div className={prefersDark ? 'dark' : 'light'}>
      {isMobile ? <MobileNav /> : <DesktopNav />}
    </div>
  );
}

Rules of Hooks

1. Only call hooks at the top level - Don't call in loops, conditions, or nested functions 2. Only call hooks from React functions - Call from components or custom hooks 3. Start custom hook names with "use" - Convention that enables linting

// Bad - conditional hook call
function Bad({ condition }) {
  if (condition) {
    const [state, setState] = useState(0); // Error!
  }
}

// Good - call hook unconditionally
function Good({ condition }) {
  const [state, setState] = useState(0);
  if (condition) {
    // Use state here
  }
}

Additional References

For production-ready custom hook implementations, see:

  • references/custom-hooks-library.md - Complete library of reusable custom hooks

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.