طراحی حالت‌های Loading، Error و Empty

در درس قبل، سه حالت اصلی یک درخواست را دیدیم: بارگذاری، موفقیت و خطا. اما یک حالت چهارم هم وجود دارد که اغلب نادیده گرفته می‌شود: حالتی که درخواست موفق بوده، اما نتیجه‌ای برای نمایش وجود ندارد. در این درس، هر چهار حالت را با دقت بیشتری طراحی می‌کنیم.

چهار حالت، نه سه حالت

یک رابط کاربری که داده را از سرور نمایش می‌دهد، معمولاً باید این چهار وضعیت را پوشش دهد:

  • Loading: درخواست هنوز در حال انجام است.
  • Error: درخواست با شکست مواجه شده است.
  • Empty: درخواست موفق بوده، اما نتیجه خالی است (مثلاً لیست محصولات صفر آیتم دارد).
  • Success با داده: درخواست موفق بوده و داده‌ای برای نمایش وجود دارد.

فراموش کردن حالت Empty، یک اشتباه رایج است؛ بدون آن، کاربر با یک صفحه خالی و بدون توضیح روبه‌رو می‌شود که نمی‌داند آیا چیزی اشتباه پیش رفته یا واقعاً نتیجه‌ای وجود ندارد.

پیاده‌سازی هر چهار حالت

function ProductList({ category }) {
  const [products, setProducts] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    setProducts(null);
    setError(null);

    fetchProducts(category)
      .then(setProducts)
      .catch((err) => setError(err.message));
  }, [category]);

  if (error) {
    return <p>مشکلی پیش آمد: {error}</p>;
  }

  if (products === null) {
    return <p>در حال بارگذاری محصولات...</p>;
  }

  if (products.length === 0) {
    return <p>محصولی در این دسته یافت نشد.</p>;
  }

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

نکته مهم در این کد، استفاده از null به‌عنوان مقدار اولیه products است، نه آرایه خالی. اگر مقدار اولیه آرایه خالی بود، نمی‌توانستیم بین «هنوز داده نیامده» و «داده آمده ولی خالی است» تفاوت بگذاریم؛ هر دو حالت یک آرایه با طول صفر داشتند.

ترتیب بررسی شرط‌ها اهمیت دارد

در این کد، ترتیب بررسی عمدی است: ابتدا Error، چون اگر خطا رخ داده، دیگر مهم نیست products چه مقداری دارد. سپس Loading، چون تا وقتی products هنوز null است، چیزی برای بررسی Empty یا نمایش وجود ندارد. این توالی، از نمایش هم‌زمان یا متناقض چند پیام جلوگیری می‌کند.

طراحی پیام Empty، نه فقط نمایش آن

حالت Empty، فرصت خوبی برای راهنمایی کاربر است؛ به‌جای یک جمله خشک مثل «چیزی یافت نشد»، می‌توان پیشنهاد عملی هم داد، مثلاً «فیلترها را پاک کنید» یا «دسته دیگری را امتحان کنید». این تصمیم طراحی، ربطی به React ندارد، اما نادیده گرفتن آن، تجربه کاربری ضعیفی می‌سازد.

Loading اولیه در برابر Loading هنگام تغییر

یک نکته ظریف دیگر: وقتی category تغییر می‌کند، کد بالا دوباره products را null می‌کند؛ یعنی کل لیست قبلی ناپدید شده و دوباره پیام Loading نشان داده می‌شود. این رفتار گاهی مطلوب نیست؛ در فصل‌های بعدی (مانند بحث Caching) خواهیم دید چطور می‌توان داده قبلی را تا رسیدن داده جدید نگه داشت تا صفحه ناگهان خالی نشود.

مثال قابل اجرا

در این مثال، هر چهار حالت قابل مشاهده‌اند؛ دسته‌ای را انتخاب کنید که نتیجه خالی دارد تا حالت Empty را هم ببینید.

پیاده‌سازی کامل چهار حالت یک درخواست
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

یک رابط کاربری کامل برای داده دریافتی از سرور، باید چهار حالت را پوشش دهد: Loading، Error، Empty و Success با داده. استفاده از null به‌جای آرایه خالی برای وضعیت اولیه، به تفکیک «هنوز نیامده» از «خالی است» کمک می‌کند. در درس بعدی، به چرخه حیات کامل یک Request، از شروع تا پایان، می‌پردازیم.