Data Fetching: دریافت داده از سرور در React

تا اینجا در این دوره، چند بار به‌طور مختصر از fetch برای گرفتن داده استفاده کردیم (مثلاً در فصل دهم، داخل useFetch). از این فصل به بعد، به‌طور کامل به دریافت داده می‌پردازیم: چگونه یک درخواست را به‌درستی داخل یک کامپوننت مدیریت کنیم و چه نکاتی را باید رعایت کنیم.

کجای کامپوننت باید درخواست ارسال شود

همان‌طور که در فصل ششم دیدیم، ارسال درخواست به سرور یک عمل جانبی (Side Effect) است؛ چون چیزی خارج از React را درگیر می‌کند. به همین دلیل، جای طبیعی آن useEffect است، نه بدنه اصلی کامپوننت:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then((res) => res.json())
      .then((data) => setUser(data));
  }, [userId]);

  return <p>{user?.name}</p>;
}

سه حالت اصلی یک درخواست

هر درخواست به سرور، معمولاً سه حالت ممکن دارد که باید در رابط کاربری نمایش داده شوند: در حال بارگذاری، موفق با داده، یا ناموفق با خطا. برای مدیریت این سه حالت، معمولاً به بیش از یک State نیاز داریم:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    setError(null);

    fetch(`/api/users/${userId}`)
      .then((res) => {
        if (!res.ok) throw new Error("خطا در دریافت داده");
        return res.json();
      })
      .then((data) => setUser(data))
      .catch((err) => setError(err.message))
      .finally(() => setLoading(false));
  }, [userId]);

  if (loading) return <p>در حال بارگذاری...</p>;
  if (error) return <p>خطا: {error}</p>;
  return <p>{user.name}</p>;
}

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

یادآوری مهم: Race Condition را فراموش نکنید

همان‌طور که در فصل ششم با جزئیات دیدیم، اگر userId به‌سرعت چند بار عوض شود، ممکن است پاسخ یک درخواست قدیمی، دیرتر از یک درخواست جدیدتر برسد و اشتباهاً روی State اعمال شود. کد بالا این مشکل را ندارد؛ راه‌حل کامل آن (با پرچم ignore) در درس چهارم این فصل، دوباره و با جزئیات بیشتر مرور می‌شود.

چرا fetch خام، معمولاً کافی نیست

نوشتن مستقیم fetch داخل هر کامپوننتی که به داده نیاز دارد، باعث تکرار همین الگوی سه‌حالتی در جاهای مختلف می‌شود. همان‌طور که در فصل دهم با Custom Hookها دیدیم، منطق تکراری معمولاً باید استخراج شود؛ موضوعی که در درس هفتم و هشتم همین فصل، هم با یک Custom Hook دستی و هم با معرفی کتابخانه‌های تخصصی‌تر دنبال می‌شود.

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

در این مثال از یک تابع fetch ساختگی استفاده شده تا بدون نیاز به سرور واقعی، رفتار کامل یک درخواست را ببینید.

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

جمع‌بندی

دریافت داده در React، معمولاً داخل یک useEffect وابسته به ورودی مرتبط (مثل userId) انجام می‌شود و باید سه حالت بارگذاری، موفقیت و خطا را پوشش دهد. نوشتن مستقیم fetch در هر کامپوننت، منطق را تکراری می‌کند؛ راه‌حل‌های آن در درس‌های بعدی همین فصل بررسی می‌شوند. در درس بعدی، دقیق‌تر به طراحی حالت‌های Loading، Error و Empty می‌پردازیم.