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.