Agent skill

react-hooks

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/react-hooks

SKILL.md

React Hooks Skill

Purpose

Master React hooks patterns including useState, useEffect, useContext, useMemo, useCallback, and custom hooks for the music-app project.

Core Hooks

useState

javascript
function Counter() {
  const [count, setCount] = useState(0);

  // Functional update
  setCount(prev => prev + 1);

  // Object state
  const [user, setUser] = useState({ name: '', email: '' });
  setUser(prev => ({ ...prev, email: 'new@email.com' }));

  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

useEffect

javascript
// Run once on mount
useEffect(() => {
  fetchData();
}, []);

// Run when dependency changes
useEffect(() => {
  console.log('Count changed:', count);
}, [count]);

// Cleanup
useEffect(() => {
  const subscription = subscribe();
  return () => subscription.unsubscribe();
}, []);

// Async in useEffect
useEffect(() => {
  let isMounted = true;

  async function loadData() {
    const data = await fetchData();
    if (isMounted) setState(data);
  }

  loadData();
  return () => { isMounted = false; };
}, []);

useContext Pattern

javascript
// Define context and hook together
import { createContext, useContext } from 'react';

const AuthContext = createContext();

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

// Usage in components
function Component() {
  const { user, login, logout } = useAuth();
  // ...
}

useMemo

javascript
// Expensive computation
const filteredTunes = useMemo(() => {
  return tunes.filter(tune =>
    tune.genre === selectedGenre &&
    tune.rhythm === selectedRhythm
  );
}, [tunes, selectedGenre, selectedRhythm]);

// Derived state
const tuneCount = useMemo(() => filteredTunes.length, [filteredTunes]);

useCallback

javascript
// Stable function reference
const handleAddFavorite = useCallback((tuneId) => {
  setFavorites(prev => [...prev, tuneId]);
}, []);

// Pass to child components
<TuneCard
  tune={tune}
  onFavorite={handleAddFavorite} // Stable reference
/>

// With dependencies
const handleSearch = useCallback((query) => {
  performSearch(query, filters);
}, [filters]);

Custom Hooks

useLocalStorage

javascript
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

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

  return [value, setValue];
}

// Usage
const [favorites, setFavorites] = useLocalStorage('favorites', []);

useDebounce

javascript
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

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

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

  return debouncedValue;
}

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

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

Best Practices

Do:

  • Always include dependencies in dependency arrays
  • Clean up side effects in return function
  • Use functional updates when new state depends on old state
  • Extract reusable logic into custom hooks
  • Memoize expensive computations and callbacks

Don't:

  • Mutate state directly
  • Call hooks conditionally
  • Skip dependency arrays (eslint-disable is a code smell)
  • Forget to clean up listeners/timers
  • Overuse useMemo/useCallback (only when needed)

Common Patterns

Data Fetching

javascript
function useTunes() {
  const [tunes, setTunes] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchTunes() {
      try {
        const data = await getTunes();
        setTunes(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchTunes();
  }, []);

  return { tunes, loading, error };
}

Form Handling

javascript
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);

  const handleChange = useCallback((e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
  }, []);

  const reset = useCallback(() => {
    setValues(initialValues);
  }, [initialValues]);

  return { values, handleChange, reset };
}

Project Patterns

Context Usage

javascript
// Always use provided hooks, never direct context access
const { user } = useAuth();           // ✓
const { favorites } = useFavorites(); // ✓

// Not this
const context = useContext(AuthContext); // ✗

Effect Dependencies

javascript
// Include all dependencies
useEffect(() => {
  if (user) {
    loadFavorites(user.uid);
  }
}, [user]); // Include user

// Use exhaustive-deps lint rule
// eslint-plugin-react-hooks

Common Issues

Issue: Infinite loop in useEffect Solution: Check dependencies, ensure not setting state that's in dependency array

Issue: Stale closure Solution: Include all dependencies or use functional state updates

Issue: "Can't perform state update on unmounted component" Solution: Track mounted state and check before setState

Didn't find tool you were looking for?

Be as detailed as possible for better results