Ders İçeriği

Veri Çekme (Fetch API, Axios)

Modern web uygulamalarının temel gereksinimlerinden biri, sunucudan veri çekmek ve sunucuya veri göndermektir. React uygulamalarında API entegrasyonu yapmak için çeşitli yöntemler bulunur. En yaygın kullanılan yöntemler Fetch API ve Axios kütüphanesidir.

Fetch API

Fetch API, modern tarayıcılarda yerleşik olarak bulunan ve HTTP istekleri yapmak için kullanılan bir JavaScript API'sidir. Promise tabanlı çalışır ve async/await ile kullanılabilir.

Basit bir GET isteği örneği:

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


function UserList() {

  const [users, setUsers] = useState([]);

  const [loading, setLoading] = useState(true);

  const [error, setError] = useState(null);

  

  useEffect(() => {

    const fetchUsers = async () => {

      try {

        setLoading(true);

        const response = await fetch('https://jsonplaceholder.typicode.com/users');

        

        if (!response.ok) {

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

        }

        

        const userData = await response.json();

        setUsers(userData);

      } catch (err) {

        setError(err.message);

      } finally {

        setLoading(false);

      }

    };

    

    fetchUsers();

  }, []);

  

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

  if (error) return <div>Hata: {error}</div>;

  

  return (

    <div>

      <h2>Kullanıcı Listesi</h2>

      <ul>

        {users.map(user => (

          <li key={user.id}>

            <strong>{user.name}</strong> - {user.email}

          </li>

        ))}

      </ul>

    </div>

  );

}

Fetch API ile POST isteği yapmak:

function CreateUser() {

  const [formData, setFormData] = useState({

    name: '',

    email: '',

    phone: ''

  });

  const [isSubmitting, setIsSubmitting] = useState(false);

  const [message, setMessage] = useState('');

  

  const handleSubmit = async (event) => {

    event.preventDefault();

    setIsSubmitting(true);

    setMessage('');

    

    try {

      const response = await fetch('https://jsonplaceholder.typicode.com/users', {

        method: 'POST',

        headers: {

          'Content-Type': 'application/json',

        },

        body: JSON.stringify(formData)

      });

      

      if (!response.ok) {

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

      }

      

      const result = await response.json();

      setMessage('Kullanıcı başarıyla oluşturuldu!');

      setFormData({ name: '', email: '', phone: '' });

      console.log('Oluşturulan kullanıcı:', result);

    } catch (error) {

      setMessage(`Hata: ${error.message}`);

    } finally {

      setIsSubmitting(false);

    }

  };

  

  return (

    <form onSubmit={handleSubmit}>

      <div>

        <label>

          İsim:

          <input

            type="text"

            value={formData.name}

            onChange={(e) => setFormData({...formData, name: e.target.value})}

            required

          />

        </label>

      </div>

      

      <div>

        <label>

          E-posta:

          <input

            type="email"

            value={formData.email}

            onChange={(e) => setFormData({...formData, email: e.target.value})}

            required

          />

        </label>

      </div>

      

      <div>

        <label>

          Telefon:

          <input

            type="tel"

            value={formData.phone}

            onChange={(e) => setFormData({...formData, phone: e.target.value})}

            required

          />

        </label>

      </div>

      

      <button type="submit" disabled={isSubmitting}>

        {isSubmitting ? 'Gönderiliyor...' : 'Kullanıcı Oluştur'}

      </button>

      

      {message && <p>{message}</p>}

    </form>

  );

}

Axios Kütüphanesi

Axios, HTTP istekleri yapmak için popüler bir JavaScript kütüphanesidir. Fetch API'ye göre daha fazla özellik sunar ve kullanımı daha kolaydır.

Axios kurulumu:

bash npm install axios

Axios ile GET isteği:

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

import axios from 'axios';


function PostList() {

  const [posts, setPosts] = useState([]);

  const [loading, setLoading] = useState(true);

  const [error, setError] = useState(null);

  

  useEffect(() => {

    const fetchPosts = async () => {

      try {

        setLoading(true);

        const response = await axios.get('https://jsonplaceholder.typicode.com/posts');

        setPosts(response.data);

      } catch (err) {

        setError(err.response?.data?.message || err.message);

      } finally {

        setLoading(false);

      }

    };

    

    fetchPosts();

  }, []);

  

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

  if (error) return <div>Hata: {error}</div>;

  

  return (

    <div>

      <h2>Gönderi Listesi</h2>

      {posts.slice(0, 10).map(post => (

        <div key={post.id} style={{ marginBottom: '20px', padding: '10px', border: '1px solid #ccc' }}>

          <h3>{post.title}</h3>

          <p>{post.body}</p>

        </div>

      ))}

    </div>

  );

}

Axios ile POST, PUT, DELETE istekleri:

import axios from 'axios';


// Axios instance oluşturma (base URL ve ortak ayarlar için)

const api = axios.create({

  baseURL: 'https://jsonplaceholder.typicode.com',

  timeout: 10000,

  headers: {

    'Content-Type': 'application/json',

  }

});


// Request interceptor (her istekten önce çalışır)

api.interceptors.request.use(

  (config) => {

    // Auth token ekleme

    const token = localStorage.getItem('authToken');

    if (token) {

      config.headers.Authorization = `Bearer ${token}`;

    }

    return config;

  },

  (error) => {

    return Promise.reject(error);

  }

);


// Response interceptor (her yanıttan sonra çalışır)

api.interceptors.response.use(

  (response) => {

    return response;

  },

  (error) => {

    if (error.response?.status === 401) {

      // Unauthorized - kullanıcıyı login sayfasına yönlendir

      localStorage.removeItem('authToken');

      window.location.href = '/login';

    }

    return Promise.reject(error);

  }

);


function PostManager() {

  const [posts, setPosts] = useState([]);

  const [loading, setLoading] = useState(false);

  const [editingPost, setEditingPost] = useState(null);

  

  // Gönderi oluşturma

  const createPost = async (postData) => {

    try {

      setLoading(true);

      const response = await api.post('/posts', postData);

      setPosts(prev => [response.data, ...prev]);

      return response.data;

    } catch (error) {

      throw new Error(error.response?.data?.message || 'Gönderi oluşturulamadı');

    } finally {

      setLoading(false);

    }

  };

  

  // Gönderi güncelleme

  const updatePost = async (id, postData) => {

    try {

      setLoading(true);

      const response = await api.put(`/posts/${id}`, postData);

      setPosts(prev => prev.map(post => 

        post.id === id ? response.data : post

      ));

      return response.data;

    } catch (error) {

      throw new Error(error.response?.data?.message || 'Gönderi güncellenemedi');

    } finally {

      setLoading(false);

    }

  };

  

  // Gönderi silme

  const deletePost = async (id) => {

    try {

      setLoading(true);

      await api.delete(`/posts/${id}`);

      setPosts(prev => prev.filter(post => post.id !== id));

    } catch (error) {

      throw new Error(error.response?.data?.message || 'Gönderi silinemedi');

    } finally {

      setLoading(false);

    }

  };

  

  // Gönderileri yükleme

  const fetchPosts = async () => {

    try {

      setLoading(true);

      const response = await api.get('/posts');

      setPosts(response.data.slice(0, 10)); // İlk 10 gönderi

    } catch (error) {

      console.error('Gönderiler yüklenemedi:', error);

    } finally {

      setLoading(false);

    }

  };

  

  useEffect(() => {

    fetchPosts();

  }, []);

  

  return (

    <div>

      <h2>Gönderi Yöneticisi</h2>

      

      <PostForm 

        onSubmit={createPost}

        loading={loading}

      />

      

      {editingPost && (

        <PostEditForm

          post={editingPost}

          onSubmit={(data) => updatePost(editingPost.id, data)}

          onCancel={() => setEditingPost(null)}

          loading={loading}

        />

      )}

      

      <div>

        {posts.map(post => (

          <div key={post.id} style={{ marginBottom: '20px', padding: '10px', border: '1px solid #ccc' }}>

            <h3>{post.title}</h3>

            <p>{post.body}</p>

            <button onClick={() => setEditingPost(post)}>Düzenle</button>

            <button 

              onClick={() => deletePost(post.id)}

              style={{ marginLeft: '10px', color: 'red' }}

            >

              Sil

            </button>

          </div>

        ))}

      </div>

    </div>

  );

}

Asenkron İşlemler

React uygulamalarında API çağrıları asenkron işlemlerdir ve bu işlemlerin doğru yönetilmesi kritiktir. Asenkron işlemleri yönetmek için çeşitli yaklaşımlar kullanılabilir.

useEffect ile Asenkron İşlemler

useEffect hook'u içinde asenkron işlemler yaparken dikkat edilmesi gereken noktalar vardır:

function DataComponent() {

  const [data, setData] = useState(null);

  const [loading, setLoading] = useState(true);

  const [error, setError] = useState(null);

  

  useEffect(() => {

    let isCancelled = false; // Cleanup için flag

    

    const fetchData = async () => {

      try {

        setLoading(true);

        setError(null);

        

        const response = await fetch('/api/data');

        const result = await response.json();

        

        // Component unmount olduysa state güncelleme

        if (!isCancelled) {

          setData(result);

        }

      } catch (err) {

        if (!isCancelled) {

          setError(err.message);

        }

      } finally {

        if (!isCancelled) {

          setLoading(false);

        }

      }

    };

    

    fetchData();

    

    // Cleanup function

    return () => {

      isCancelled = true;

    };

  }, []);

  

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

  if (error) return <div>Hata: {error}</div>;

  if (!data) return <div>Veri bulunamadı</div>;

  

  return (

    <div>

      <h2>Veri</h2>

      <pre>{JSON.stringify(data, null, 2)}</pre>

    </div>

  );

}

AbortController ile İstek İptal Etme

Uzun süren istekleri iptal etmek için AbortController kullanılabilir:

function SearchComponent() {

  const [query, setQuery] = useState('');

  const [results, setResults] = useState([]);

  const [loading, setLoading] = useState(false);

  const [error, setError] = useState(null);

  

  useEffect(() => {

    if (!query.trim()) {

      setResults([]);

      return;

    }

    

    const controller = new AbortController();

    

    const searchData = async () => {

      try {

        setLoading(true);

        setError(null);

        

        const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {

          signal: controller.signal

        });

        

        if (!response.ok) {

          throw new Error('Arama başarısız');

        }

        

        const data = await response.json();

        setResults(data);

      } catch (err) {

        if (err.name !== 'AbortError') {

          setError(err.message);

        }

      } finally {

        setLoading(false);

      }

    };

    

    // Debounce: 500ms bekle

    const timeoutId = setTimeout(searchData, 500);

    

    return () => {

      clearTimeout(timeoutId);

      controller.abort();

    };

  }, [query]);

  

  return (

    <div>

      <input

        type="text"

        value={query}

        onChange={(e) => setQuery(e.target.value)}

        placeholder="Arama yapın..."

      />

      

      {loading && <div>Aranıyor...</div>}

      {error && <div>Hata: {error}</div>}

      

      <ul>

        {results.map(item => (

          <li key={item.id}>{item.title}</li>

        ))}

      </ul>

    </div>

  );

}

Özel API Hook'ları

API çağrılarını yönetmek için özel hook'lar oluşturmak kod tekrarını azaltır:

// useApi hook'u

function useApi(url, options = {}) {

  const [data, setData] = useState(null);

  const [loading, setLoading] = useState(true);

  const [error, setError] = useState(null);

  

  const fetchData = useCallback(async () => {

    try {

      setLoading(true);

      setError(null);

      

      const response = await fetch(url, options);

      

      if (!response.ok) {

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

      }

      

      const result = await response.json();

      setData(result);

    } catch (err) {

      setError(err.message);

    } finally {

      setLoading(false);

    }

  }, [url, options]);

  

  useEffect(() => {

    fetchData();

  }, [fetchData]);

  

  return { data, loading, error, refetch: fetchData };

}


// useMutation hook'u (POST, PUT, DELETE için)

function useMutation(mutationFn) {

  const [loading, setLoading] = useState(false);

  const [error, setError] = useState(null);

  const [data, setData] = useState(null);

  

  const mutate = useCallback(async (...args) => {

    try {

      setLoading(true);

      setError(null);

      

      const result = await mutationFn(...args);

      setData(result);

      return result;

    } catch (err) {

      setError(err.message);

      throw err;

    } finally {

      setLoading(false);

    }

  }, [mutationFn]);

  

  return { mutate, loading, error, data };

}


// Kullanım örnekleri

function UserProfile({ userId }) {

  const { data: user, loading, error, refetch } = useApi(`/api/users/${userId}`);

  

  const updateUserMutation = useMutation(async (userData) => {

    const response = await fetch(`/api/users/${userId}`, {

      method: 'PUT',

      headers: { 'Content-Type': 'application/json' },

      body: JSON.stringify(userData)

    });

    return response.json();

  });

  

  const handleUpdate = async (userData) => {

    try {

      await updateUserMutation.mutate(userData);

      refetch(); // Kullanıcı verilerini yenile

      alert('Profil güncellendi!');

    } catch (error) {

      alert('Güncelleme başarısız!');

    }

  };

  

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

  if (error) return <div>Hata: {error}</div>;

  

  return (

    <div>

      <h2>{user.name}</h2>

      <p>{user.email}</p>

      <button onClick={() => handleUpdate({ ...user, lastUpdated: new Date() })}>

        Profili Güncelle

      </button>

    </div>

  );

}

Hata Yönetimi

API çağrılarında hata yönetimi, kullanıcı deneyimi açısından kritiktir. Farklı hata türleri için farklı yaklaşımlar benimsenmelidir.

Hata Türleri ve Yönetimi

// Kapsamlı hata yönetimi hook'u

function useApiWithErrorHandling(url, options = {}) {

  const [data, setData] = useState(null);

  const [loading, setLoading] = useState(true);

  const [error, setError] = useState(null);

  const [retryCount, setRetryCount] = useState(0);

  

  const fetchData = useCallback(async (retryAttempt = 0) => {

    try {

      setLoading(true);

      setError(null);

      

      const controller = new AbortController();

      const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 saniye timeout

      

      const response = await fetch(url, {

        ...options,

        signal: controller.signal

      });

      

      clearTimeout(timeoutId);

      

      // HTTP hata kodlarını kontrol et

      if (!response.ok) {

        const errorData = await response.json().catch(() => ({}));

        

        switch (response.status) {

          case 400:

            throw new Error(errorData.message || 'Geçersiz istek');

          case 401:

            throw new Error('Yetkilendirme gerekli');

          case 403:

            throw new Error('Bu işlem için yetkiniz yok');

          case 404:

            throw new Error('Kaynak bulunamadı');

          case 429:

            throw new Error('Çok fazla istek gönderildi. Lütfen bekleyin.');

          case 500:

            throw new Error('Sunucu hatası');

          case 503:

            throw new Error('Servis geçici olarak kullanılamıyor');

          default:

            throw new Error(`HTTP ${response.status}: ${response.statusText}`);

        }

      }

      

      const result = await response.json();

      setData(result);

      setRetryCount(0); // Başarılı olursa retry sayacını sıfırla

      

    } catch (err) {

      console.error('API Error:', err);

      

      // Ağ hatası veya timeout

      if (err.name === 'AbortError') {

        setError('İstek zaman aşımına uğradı');

      } else if (err.name === 'TypeError' && err.message.includes('fetch')) {

        setError('Ağ bağlantısı hatası');

      } else {

        setError(err.message);

      }

      

      // Otomatik retry (belirli hata türleri için)

      if (retryAttempt < 3 && (

        err.name === 'AbortError' || 

        err.message.includes('Ağ') ||

        err.message.includes('503')

      )) {

        setTimeout(() => {

          setRetryCount(prev => prev + 1);

          fetchData(retryAttempt + 1);

        }, Math.pow(2, retryAttempt) * 1000); // Exponential backoff

      }

    } finally {

      setLoading(false);

    }

  }, [url, options]);

  

  useEffect(() => {

    fetchData();

  }, [fetchData]);

  

  const retry = () => {

    setRetryCount(0);

    fetchData();

  };

  

  return { data, loading, error, retry, retryCount };

}


// Error Boundary bileşeni

class ApiErrorBoundary extends React.Component {

  constructor(props) {

    super(props);

    this.state = { hasError: false, error: null };

  }

  

  static getDerivedStateFromError(error) {

    return { hasError: true, error };

  }

  

  componentDidCatch(error, errorInfo) {

    console.error('API Error Boundary:', error, errorInfo);

    

    // Hata raporlama servisi (örn. Sentry)

    // reportError(error, errorInfo);

  }

  

  render() {

    if (this.state.hasError) {

      return (

        <div style={{ padding: '20px', textAlign: 'center' }}>

          <h2>Bir şeyler ters gitti</h2>

          <p>Lütfen sayfayı yenileyin veya daha sonra tekrar deneyin.</p>

          <button onClick={() => window.location.reload()}>

            Sayfayı Yenile

          </button>

        </div>

      );

    }

    

    return this.props.children;

  }

}


// Kullanım örneği

function DataDashboard() {

  const { data, loading, error, retry, retryCount } = useApiWithErrorHandling('/api/dashboard');

  

  if (loading) {

    return (

      <div style={{ textAlign: 'center', padding: '50px' }}>

        <div>Yükleniyor...</div>

        {retryCount > 0 && <div>Yeniden deneme: {retryCount}/3</div>}

      </div>

    );

  }

  

  if (error) {

    return (

      <div style={{ textAlign: 'center', padding: '50px' }}>

        <h3>Hata Oluştu</h3>

        <p>{error}</p>

        <button onClick={retry} style={{ marginTop: '10px' }}>

          Tekrar Dene

        </button>

      </div>

    );

  }

  

  return (

    <ApiErrorBoundary>

      <div>

        <h2>Dashboard</h2>

        <pre>{JSON.stringify(data, null, 2)}</pre>

      </div>

    </ApiErrorBoundary>

  );

}

Global Hata Yönetimi

Uygulama genelinde hata yönetimi için Context API kullanılabilir:

// Error Context

const ErrorContext = createContext();


export function ErrorProvider({ children }) {

  const [errors, setErrors] = useState([]);

  

  const addError = (error) => {

    const errorObj = {

      id: Date.now(),

      message: error.message || error,

      timestamp: new Date(),

      type: error.type || 'error'

    };

    

    setErrors(prev => [...prev, errorObj]);

    

    // 5 saniye sonra otomatik kaldır

    setTimeout(() => {

      removeError(errorObj.id);

    }, 5000);

  };

  

  const removeError = (id) => {

    setErrors(prev => prev.filter(error => error.id !== id));

  };

  

  return (

    <ErrorContext.Provider value={{ errors, addError, removeError }}>

      {children}

      <ErrorNotifications errors={errors} onRemove={removeError} />

    </ErrorContext.Provider>

  );

}


export const useError = () => {

  const context = useContext(ErrorContext);

  if (!context) {

    throw new Error('useError must be used within ErrorProvider');

  }

  return context;

};


// Error Notifications bileşeni

function ErrorNotifications({ errors, onRemove }) {

  return (

    <div style={{

      position: 'fixed',

      top: '20px',

      right: '20px',

      zIndex: 1000

    }}>

      {errors.map(error => (

        <div

          key={error.id}

          style={{

            background: '#f8d7da',

            color: '#721c24',

            padding: '10px 15px',

            marginBottom: '10px',

            borderRadius: '5px',

            border: '1px solid #f5c6cb',

            maxWidth: '300px'

          }}

        >

          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>

            <span>{error.message}</span>

            <button

              onClick={() => onRemove(error.id)}

              style={{ background: 'none', border: 'none', color: '#721c24', cursor: 'pointer' }}

            >

              ×

            </button>

          </div>

        </div>

      ))}

    </div>

  );

}


// Kullanım

function App() {

  return (

    <ErrorProvider>

      <Router>

        <Routes>

          <Route path="/" element={<Home />} />

          <Route path="/users" element={<Users />} />

        </Routes>

      </Router>

    </ErrorProvider>

  );

}


function Users() {

  const [users, setUsers] = useState([]);

  const [loading, setLoading] = useState(true);

  const { addError } = useError();

  

  useEffect(() => {

    const fetchUsers = async () => {

      try {

        const response = await fetch('/api/users');

        if (!response.ok) throw new Error('Kullanıcılar yüklenemedi');

        const data = await response.json();

        setUsers(data);

      } catch (error) {

        addError(error);

      } finally {

        setLoading(false);

      }

    };

    

    fetchUsers();

  }, [addError]);

  

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

  

  return (

    <div>

      <h2>Kullanıcılar</h2>

      {users.map(user => (

        <div key={user.id}>{user.name}</div>

      ))}

    </div>

  );

}

React'te API entegrasyonu, modern web uygulamalarının temel gereksinimlerinden biridir. Doğru hata yönetimi, loading state'leri ve kullanıcı deneyimi optimizasyonları ile birlikte yapıldığında, kullanıcılar için sorunsuz bir deneyim sağlanabilir. Bu derste öğrendiğiniz kavramları kendi projelerinizde uygulayarak deneyim kazanmanız önemlidir.