🚀 Giriş

React Hooks, React 16.8 ile birlikte gelen ve functional component'lerde state ve diğer React özelliklerini kullanmamızı sağlayan güçlü bir API. Bu rehberde, en önemli hook'ları detaylıca inceleyeceğiz ve gerçek dünya örnekleriyle nasıl kullanılacağını göreceğiz.

💡 Bu yazıda öğrenecekleriniz:

  • useState ile state yönetimi
  • useEffect ile lifecycle işlemleri
  • useContext ile global state
  • Custom hook'lar nasıl yazılır
  • Hook'lar için best practice'ler

📊 useState Hook

useState hook'u, functional component'lerde state kullanmamızı sağlar. En basit ve en sık kullanılan hook'lardan biridir.

Temel Kullanım

import React, { useState } from 'react';

function Counter() {
  // State tanımlama
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Sayı: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Artır
      </button>
      <button onClick={() => setCount(count - 1)}>
        Azalt
      </button>
    </div>
  );
}

Fonksiyonel Update

State güncellemelerinde önceki değeri kullanırken, fonksiyonel update tercih edilmelidir:

// ❌ Önerilmeyen
setCount(count + 1);

// ✅ Önerilen
setCount(prevCount => prevCount + 1);

Object State Yönetimi

function UserProfile() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });

  const updateName = (newName) => {
    setUser(prevUser => ({
      ...prevUser,
      name: newName
    }));
  };

  return (
    <div>
      <input 
        value={user.name}
        onChange={(e) => updateName(e.target.value)}
        placeholder="İsim"
      />
      <p>Merhaba {user.name}!</p>
    </div>
  );
}

⚡ useEffect Hook

useEffect hook'u, side effect'leri yönetmek için kullanılır. Component mount, update ve unmount lifecycle'larını yönetir.

Temel Kullanım

import React, { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Component mount olduğunda çalışır
    fetchData();
  }, []); // Boş dependency array

  const fetchData = async () => {
    try {
      const response = await fetch('/api/data');
      const result = await response.json();
      setData(result);
    } catch (error) {
      console.error('Veri yüklenirken hata:', error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <div>Yükleniyor...</div>;

  return (
    <ul>
      {data.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Cleanup Function

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    // Cleanup function
    return () => {
      clearInterval(interval);
    };
  }, []);

  return <div>Geçen süre: {seconds} saniye</div>;
}

⚠️ Dikkat!

useEffect'te kullandığınız her değişkeni dependency array'e eklemeyi unutmayın. ESLint kuralı exhaustive-deps bu konuda yardımcı olacaktır.

🌐 useContext Hook

useContext hook'u, Context API ile oluşturulan context'leri consume etmek için kullanılır. Prop drilling problemini çözer.

Context Oluşturma

// ThemeContext.js
import React, { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Custom hook
export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

Context Kullanımı

// App.js
function App() {
  return (
    <ThemeProvider>
      <Header />
      <MainContent />
    </ThemeProvider>
  );
}

// Header.js
function Header() {
  const { theme, toggleTheme } = useTheme();

  return (
    <header className={`header header--${theme}`}>
      <h1>My App</h1>
      <button onClick={toggleTheme}>
        {theme === 'light' ? '🌙' : '☀️'}
      </button>
    </header>
  );
}

🔧 Custom Hooks

Custom hook'lar, stateful logic'i component'ler arasında paylaşmak için kullanılır. Hook kurallarına uygun şekilde yazılmalıdır.

useLocalStorage Hook

import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  // State'i localStorage'dan initialize et
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error('localStorage okuma hatası:', error);
      return initialValue;
    }
  });

  // setValue fonksiyonu
  const setValue = (value) => {
    try {
      // Functional update'i destekle
      const valueToStore = 
        value instanceof Function ? value(storedValue) : value;
      
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error('localStorage yazma hatası:', error);
    }
  };

  return [storedValue, setValue];
}

// Kullanım
function Settings() {
  const [settings, setSettings] = useLocalStorage('appSettings', {
    notifications: true,
    theme: 'light'
  });

  return (
    <div>
      <label>
        <input
          type="checkbox"
          checked={settings.notifications}
          onChange={(e) => setSettings(prev => ({
            ...prev,
            notifications: e.target.checked
          }))}
        />
        Bildirimler
      </label>
    </div>
  );
}

useFetch Hook

import { useState, useEffect } from 'react';

function useFetch(url, options = {}) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const abortController = new AbortController();

    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);

        const response = await fetch(url, {
          ...options,
          signal: abortController.signal
        });

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const result = await response.json();
        setData(result);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    // Cleanup: request'i iptal et
    return () => abortController.abort();
  }, [url]);

  return { data, loading, error };
}

// Kullanım
function UserList() {
  const { data: users, loading, error } = useFetch('/api/users');

  if (loading) return <div>Yükleniyor...</div>;
  if (error) return <div>Hata: {error}</div>;

  return (
    <ul>
      {users?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

✅ Best Practices

1. Hook Kuralları

  • Hook'ları sadece React function'ları içinde çağırın
  • Hook'ları döngü, koşul veya nested function içinde çağırmayın
  • Hook'ları her zaman component'in en üstünde kullanın

2. Performance Optimizasyonu

// useMemo ile expensive hesaplamaları cache'le
const expensiveValue = useMemo(() => {
  return data.filter(item => item.active)
             .map(item => ({ ...item, processed: true }));
}, [data]);

// useCallback ile function reference'ları stable tut
const handleClick = useCallback((id) => {
  onItemClick(id);
}, [onItemClick]);

3. Error Handling

function useAsyncOperation() {
  const [state, setState] = useState({
    data: null,
    loading: false,
    error: null
  });

  const execute = useCallback(async (operation) => {
    setState(prev => ({ ...prev, loading: true, error: null }));
    
    try {
      const result = await operation();
      setState({ data: result, loading: false, error: null });
      return result;
    } catch (error) {
      setState({ data: null, loading: false, error });
      throw error;
    }
  }, []);

  return { ...state, execute };
}

4. Testing

// Custom hook test
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

test('should increment counter', () => {
  const { result } = renderHook(() => useCounter());
  
  act(() => {
    result.current.increment();
  });
  
  expect(result.current.count).toBe(1);
});

🎯 Sonuç

React Hooks, modern React development'in vazgeçilmez parçalarıdır. Bu rehberde gördüğümüz hook'lar ve best practice'ler ile daha temiz, daha maintainable ve daha performanslı React uygulamaları geliştirebilirsiniz.