Loading, Error, Success holatlari boshqarish

~15 daq · React · Ma'lumot olish

Muammodan boshlaylik

48-darsda fetch va Axios bilan server dan ma'lumot oldik. Lekin har bir so'rovda uchta holat bo'ladi:

  1. Yuklanmoqda — so'rov yuborildi, javob kutilmoqda
  2. Xato — server javob bermadi yoki xato qaytardi
  3. 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
  • yuklanmoqda tekshiruvi xatodan oldin keladi

Keyingi dars: 50-dars: Race Condition — nima, qanday hal qilinadi, AbortController