Loading, Error, Success holatlari boshqarish
Muammodan boshlaylik
48-darsda fetch va Axios bilan server dan ma'lumot oldik.
Lekin har bir so'rovda uchta holat bo'ladi:
- Yuklanmoqda — so'rov yuborildi, javob kutilmoqda
- Xato — server javob bermadi yoki xato qaytardi
- Muvaffaqiyat — ma'lumot keldi, ko'rsatish mumkin
Ko'p dasturchilar faqat muvaffaqiyat holatini yozadi:
// ❌ Yomon — faqat muvaffaqiyat
const [mahsulotlar, setMahsulotlar] = useState([]);
useEffect(() => {
fetch('/api/mahsulotlar')
.then((j) => j.json())
.then((d) => setMahsulotlar(d));
}, []);
return <ul>{mahsulotlar.map(...)}</ul>;
Foydalanuvchi ko'radi: bo'sh sahifa — yuklanmoqda yoki xato? Hech qanday belgi yo'q. Bu yomon UX.
Uchala holatni to'g'ri boshqarish — bu darsning maqsadi.
Uchta alohida state
Eng to'g'ridan-to'g'ri yondashuv — har holat uchun alohida state:
const [malumot, setMalumot] = useState(null);
const [yuklanmoqda, setYuklanmoqda] = useState(false);
const [xato, setXato] = useState('');
Uchala state birga ishlaydi:
import { useState, useEffect } from 'react';
import axios from 'axios';
function Mahsulotlar() {
const [mahsulotlar, setMahsulotlar] = useState([]);
const [yuklanmoqda, setYuklanmoqda] = useState(true);
const [xato, setXato] = useState('');
useEffect(() => {
async function yuklash() {
try {
setYuklanmoqda(true);
setXato('');
const javob = await axios.get('/api/mahsulotlar');
setMahsulotlar(javob.data);
} catch (err) {
setXato('Ma\'lumot yuklanmadi. Qayta urinib ko\'ring.');
} finally {
setYuklanmoqda(false);
}
}
yuklash();
}, []);
if (yuklanmoqda) return <YuklanishSkeleti />;
if (xato) return <XatoBloki xabar={xato} />;
return (
<ul>
{mahsulotlar.map((m) => (
<li key={m.id}>{m.nomi}</li>
))}
</ul>
);
}
Holat ob'ekti — bitta state
Uchta alohida state o'rniga bitta ob'ektda saqlash ham mumkin. Bu ularning sinxronligini ta'minlaydi:
const [holat, setHolat] = useState({
malumot: null,
yuklanmoqda: true,
xato: '',
});
// Yuklanayotganda
setHolat({ malumot: null, yuklanmoqda: true, xato: '' });
// Muvaffaqiyatda
setHolat({ malumot: data, yuklanmoqda: false, xato: '' });
// Xatoda
setHolat({ malumot: null, yuklanmoqda: false, xato: 'Xato yuz berdi' });
Lekin bu biroz ko'proq kod talab qiladi. Kichik komponentlarda
uchta alohida state ham yaxshi ishlaydi. Murakkab hollarda
useReducer ishlatish qulay (19-20 darslar).
Yaxshi yuklanish UI lari
1. Spinner
function Spinner() {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '40px',
}}>
<div style={{
width: '32px',
height: '32px',
border: '3px solid #e2e8f0',
borderTop: '3px solid #2563eb',
borderRadius: '50%',
animation: 'spin 0.8s linear infinite',
}} />
</div>
);
}
2. Skeleton UI
Sahifaning bo'sh sxemasini ko'rsatadi — foydalanuvchi nima kutayotganini tushunadi:
function MahsulotSkeleti() {
return (
<div style={{ padding: '16px' }}>
{[1, 2, 3].map((i) => (
<div key={i} style={{
display: 'flex', gap: '12px',
marginBottom: '16px', padding: '12px',
border: '1px solid #e2e8f0', borderRadius: '8px',
}}>
{/* Rasm joyi */}
<div style={{
width: '80px', height: '80px',
background: '#e2e8f0', borderRadius: '4px',
flexShrink: 0,
}} />
{/* Matn joyi */}
<div style={{ flex: 1 }}>
<div style={{
height: '16px', background: '#e2e8f0',
borderRadius: '4px', marginBottom: '8px', width: '60%',
}} />
<div style={{
height: '12px', background: '#e2e8f0',
borderRadius: '4px', width: '40%',
}} />
</div>
</div>
))}
</div>
);
}
Skeleton real ma'lumot kelganda qanday ko'rinishni oldindan ko'rsatadi — bu foydalanuvchi uchun ancha yoqimli tajriba.
Yaxshi xato UI si
Xato faqat matn emas — foydalanuvchi nima qilishi kerakligini ham ko'rsatish kerak:
function XatoBloki({ xabar, qaytaUrinish }) {
return (
<div style={{
padding: '24px',
textAlign: 'center',
border: '1px solid #fca5a5',
borderRadius: '8px',
background: '#fef2f2',
}}>
<p style={{ color: '#dc2626', marginBottom: '12px' }}>
{xabar}
</p>
{qaytaUrinish && (
<button
onClick={qaytaUrinish}
style={{
padding: '8px 16px',
background: '#2563eb',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Qayta urinish
</button>
)}
</div>
);
}
Qayta urinish — retry
Xato bo'lganda foydalanuvchi o'zi qayta so'rov yuborishi kerak:
function Mahsulotlar() {
const [mahsulotlar, setMahsulotlar] = useState([]);
const [yuklanmoqda, setYuklanmoqda] = useState(true);
const [xato, setXato] = useState('');
async function yuklash() { // ← useEffect tashqarisida, qayta chaqirish uchun
try {
setYuklanmoqda(true);
setXato('');
const javob = await axios.get('/api/mahsulotlar');
setMahsulotlar(javob.data);
} catch {
setXato('Xato yuz berdi. Internet aloqangizni tekshiring.');
} finally {
setYuklanmoqda(false);
}
}
useEffect(() => {
yuklash();
}, []);
if (yuklanmoqda) return <MahsulotSkeleti />;
if (xato) {
return (
<XatoBloki
xabar={xato}
qaytaUrinish={yuklash} // ← tugma bosilganda yuklash qayta ishlaydi
/>
);
}
return (
<ul>
{mahsulotlar.map((m) => (
<li key={m.id}>{m.nomi}</li>
))}
</ul>
);
}
Bo'sh holat — empty state
Ma'lumot keldi, lekin ro'yxat bo'sh. Bu ham alohida holatdir:
if (yuklanmoqda) return <MahsulotSkeleti />;
if (xato) return <XatoBloki xabar={xato} qaytaUrinish={yuklash} />;
// ← bo'sh holat
if (mahsulotlar.length === 0) {
return (
<div style={{ textAlign: 'center', padding: '40px', color: '#6b7280' }}>
<p>Hozircha mahsulotlar yo'q.</p>
<p>Birinchi mahsulotni qo'shing!</p>
</div>
);
}
return <ul>{mahsulotlar.map(...)}</ul>;
Ro'yxat bo'sh bo'lganda foydalanuvchi "xato bo'ldimi?" deb o'ylamasligi uchun aniq xabar ko'rsating.
Custom hook — qayta ishlatiladigan yuklash logikasi
Bir xil yuklash logikasi ko'p komponentda takrorlanadi. Custom hook bilan bir joyga to'plash mumkin:
// hooks/useFetch.js
import { useState, useEffect } from 'react';
import axios from 'axios';
function useFetch(url) {
const [malumot, setMalumot] = useState(null);
const [yuklanmoqda, setYuklanmoqda] = useState(true);
const [xato, setXato] = useState('');
useEffect(() => {
if (!url) return;
async function yuklash() {
try {
setYuklanmoqda(true);
setXato('');
const javob = await axios.get(url);
setMalumot(javob.data);
} catch (err) {
setXato(err.response?.data?.xabar || 'Xato yuz berdi');
} finally {
setYuklanmoqda(false);
}
}
yuklash();
}, [url]);
return { malumot, yuklanmoqda, xato };
}
export default useFetch;
// Komponentda — juda sodda
import useFetch from '../hooks/useFetch';
function Mahsulotlar() {
const { malumot, yuklanmoqda, xato } = useFetch('/api/mahsulotlar');
if (yuklanmoqda) return <MahsulotSkeleti />;
if (xato) return <XatoBloki xabar={xato} />;
if (!malumot?.length) return <BoshHolat />;
return <ul>{malumot.map((m) => <li key={m.id}>{m.nomi}</li>)}</ul>;
}
function Foydalanuvchilar() {
const { malumot, yuklanmoqda, xato } = useFetch('/api/foydalanuvchilar');
// xuddi shu pattern — takrorlanmaydi
}
Tez-tez uchraydigan xatolar
1. finally ni unutish
// ❌ Xato — xato bo'lsa yuklanmoqda "true" da qoladi
async function yuklash() {
try {
setYuklanmoqda(true);
const javob = await axios.get(url);
setMalumot(javob.data);
setYuklanmoqda(false); // ← faqat muvaffaqiyatda
} catch (err) {
setXato(err.message);
// yuklanmoqda hali "true"! Spinner to'xtamaydi
}
}
// ✅ To'g'ri — finally har doim ishlaydi
async function yuklash() {
try {
setYuklanmoqda(true);
const javob = await axios.get(url);
setMalumot(javob.data);
} catch (err) {
setXato(err.message);
} finally {
setYuklanmoqda(false); // ← muvaffaqiyatli ham, xato ham
}
}
Sababi: Xato bo'lganda setYuklanmoqda(false) chaqirilmasa,
spinner cheksiz aylanib qoladi. finally bloki xato bo'lsa ham,
bo'lmasa ham ishlaydi.
2. Eski xatoni tozalamaslik
// ❌ Muammo — birinchi so'rovda xato, ikkinchisida muvaffaqiyat
// lekin xato xabari ekranda qoladi
async function yuklash() {
try {
const javob = await axios.get(url);
setMalumot(javob.data);
// xato state tozalanmadi!
} catch (err) {
setXato(err.message);
}
}
// ✅ To'g'ri — har yangi so'rovda xatoni tozalash
async function yuklash() {
try {
setXato(''); // ← eski xatoni o'chir
setYuklanmoqda(true);
const javob = await axios.get(url);
setMalumot(javob.data);
} catch (err) {
setXato(err.message);
} finally {
setYuklanmoqda(false);
}
}
Sababi: Foydalanuvchi "Qayta urinish" bosganda — avvalgi
xato state da qoladi. Yangi so'rov muvaffaqiyatli bo'lsa ham
xato xabari ko'rinib turadi. Har yangi so'rov boshida setXato('')
chaqirilsin.
3. Yuklanish va xato holatini bir vaqtda ko'rsatish
// ❌ Muammo — noto'g'ri tartib
if (xato) return <XatoBloki />;
if (yuklanmoqda) return <Spinner />;
// Birinchi yuklanishda: yuklanmoqda=true, xato=''
// Xato bo'lsa: yuklanmoqda=false, xato='...'
// Qayta urinishda: yuklanmoqda=true, xato='...' ← ikkalasi ham bor!
// Xato bloki ko'rinadi, lekin yuklanmoqda ham true!
// ✅ To'g'ri tartib
if (yuklanmoqda) return <Spinner />; // ← yuklanish birinchi
if (xato) return <XatoBloki />;
return <MalumotKo'rinishi />;
Sababi: Qayta urinishda ikkalasi ham true bo'lishi mumkin.
yuklanmoqda birinchi tekshirilsa — spinner ko'rsatiladi (to'g'ri).
xato birinchi tekshirilsa — xato bloki ko'rsatiladi (noto'g'ri).
Xulosa
- Har HTTP so'rovda uchta holat:
yuklanmoqda,xato,malumot finally—yuklanmoqda(false)har doim bu yerda- Skeleton UI — spinner dan yaxshiroq UX
- Xato bloki — xabar + "Qayta urinish" tugmasi
- Bo'sh holat —
malumot.length === 0— alohida ko'rsatiladi useFetch— takroriy logikani custom hook ga ko'chirish- Har yangi so'rovda
setXato('')— eski xatoni tozalash yuklanmoqdatekshiruvi xatodan oldin keladi
Keyingi dars: 50-dars: Race Condition — nima, qanday hal qilinadi, AbortController