← Zurück

React Hooks

Custom Hooks & Patterns für sauberen React Code.

⚠️ Die 2 Regeln

  1. 1. Hooks nur auf Top-Level aufrufen — nie in Loops, Conditions, nested Functions
  2. 2. Hooks nur aus React Functions aufrufen — Components oder andere Hooks

Custom Hook Beispiele

useMousePosition

Trackt die Mausposition — Event Listener Setup & Cleanup Pattern.

function useMousePosition() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handler = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    
    window.addEventListener('mousemove', handler);
    return () => window.removeEventListener('mousemove', handler);
  }, []);

  return position;
}

// Usage
const { x, y } = useMousePosition();

useFetch

Data Fetching mit Loading, Error & Cleanup.

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let isMounted = true;  // Prevent state update on unmount
    
    async function fetchData() {
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const json = await res.json();
        if (isMounted) {
          setData(json);
          setLoading(false);
        }
      } catch (err) {
        if (isMounted) {
          setError(err.message);
          setLoading(false);
        }
      }
    }
    
    fetchData();
    return () => { isMounted = false; };
  }, [url]);

  return { data, loading, error };
}

// Usage
const { data, loading, error } = useFetch('/api/users');

useLocalStorage

State persistieren — synct automatisch mit localStorage.

function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    if (typeof window === 'undefined') return initial;
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initial;
    } catch {
      return initial;
    }
  });

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

  return [value, setValue] as const;
}

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

useDebounce

Verzögert einen Wert — perfekt für Search Inputs.

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

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

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

  return debouncedValue;
}

// Usage
const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 300);

useEffect(() => {
  // API call nur wenn User aufhört zu tippen
  fetchResults(debouncedSearch);
}, [debouncedSearch]);

useOnClickOutside

Callback wenn außerhalb eines Elements geklickt wird — für Dropdowns, Modals.

function useOnClickOutside(
  ref: RefObject<HTMLElement>, 
  handler: () => void
) {
  useEffect(() => {
    const listener = (e: MouseEvent) => {
      if (!ref.current || ref.current.contains(e.target as Node)) {
        return;
      }
      handler();
    };

    document.addEventListener('mousedown', listener);
    return () => document.removeEventListener('mousedown', listener);
  }, [ref, handler]);
}

// Usage
const dropdownRef = useRef(null);
useOnClickOutside(dropdownRef, () => setIsOpen(false));

Best Practices

Hooks mit "use" prefixen — useWhatever
Cleanup in useEffect — Memory Leaks vermeiden
isMounted Pattern — State Updates nach Unmount verhindern
useCallback für Handler — Unnötige Re-Renders vermeiden
Premature Abstraction — Hooks nur wenn sie echten Mehrwert bieten

💡 Key Insight

Custom Hooks teilen Logik, nicht State. Jede Komponente die einen Hook aufruft bekommt ihre eigene isolierte State-Kopie.