فصل ضمیمه: حذف کامل آیتم‌ها و modal خطا

Appendix: Complete Deletion of Items

22 بهمن 1399
فصل ضمیمه: حذف کامل آیتم ها و modal خطا

حالا که جست و جوی ما کار می کند نوبت به حذف محتویات است. اگر یادتان باشد درون فایل ingredients.js کد زیر را برای حذف محتویات نوشته بودیم:

  const removeIngredientHandler = ingredientId => {
    setUserIngredients(prevIngredients =>
      prevIngredients.filter(ingredient => ingredient.id !== ingredientId)
    );
  };

اما این متد تنها به صورت محلی عمل می کند، یعنی اگر محتویات را با کلیک روی آن ها حذف کنیم اما صفحه را refresh کنیم باز هم محتویات را خواهیم دید چرا که از سرور firebase حذف نشده اند. برای انجام این کار یک درخواست دیگر fetch را به Firebase ارسال می کنیم. من متد بالا را به شکل زیر تغییر می دهم:

  const removeIngredientHandler = ingredientId => {
    fetch(
      `https://react-hooks-update.firebaseio.com/ingredients/${ingredientId}.jon`,
      {
        method: 'DELETE'
      }
    ).then(response => {
      setUserIngredients(prevIngredients =>
        prevIngredients.filter(ingredient => ingredient.id !== ingredientId)
      );
    });
  };

ابتدا دستور fetch را داریم که url آن تغییر کرده است. اگر بخواهیم عنصری را از firebase حذف کنیم باید حتما id آن را نیز ارسال کنیم که من نیز این کار را انجام داده ام. سپس متد را به DELETE تغییر داده و کد های حذف محتویات به صورت محلی را در یک then گذاشته ام. چرا؟ به دلیل اینکه پس از حذف عنصر به پاسخ firebase نیازی ندارم. اگر به مرورگر بروید و کد بالا را تست کنید، نباید هیچ مشکلی در برنامه شما باشد. با کلیک روی هر کدام از محتویات خود (هر چیزی که اضافه کرده باشید) باید بتوانید آن را از Firebase حذف کنید تا با refresh شدن صفحه برنگردند.

مشکل اینجاست که در کد بالا اصلا حواسمان به خطاهای احتمالی نیست. اولا در برخی از اوقات ارسال درخواست به firebase و حذف آن طول می کشد بنابراین بهتر است تا در حین انجام عملیات حذف یک spinner (علامت loading) نشان بدهیم. دوما اگر درخواست ما به خطا بخورد، هیچ کدی برای مدیریت این خطا نداریم تا مثلا یک error modal را نمایش دهیم.

ابتدا مشکل spinner را حل می کنیم. اولین مرحله ایجاد یک state جدید در فایل Ingredients.js است:

  const [isLoading, setIsLoading] = useState(false);

حالا زمانی که در حال بارگذاری محتویات باشیم، باید setIsLoading را روی true قرار دهیم:

  const addIngredientHandler = ingredient => {
    setIsLoading(true);
    fetch('https://react-hooks-update.firebaseio.com/ingredients.json', {
      method: 'POST',
      body: JSON.stringify(ingredient),
      headers: { 'Content-Type': 'application/json' }
    })
      .then(response => {
        setIsLoading(false);
        return response.json();
      })
// بقیه کد ها //

یعنی در ابتدای بارگذاری، setIsLoading برابر true و پس از بارگذاری (درون then) مقدار false را به setIsLoading می دهیم. حالا به قسمت JSX همین فایل می رویم و یک prop جدید به نام loading را به IngredientForm اضافه می کنم:

<IngredientForm
    onAddIngredient={addIngredientHandler}
    loading={isLoading}
/>

حالا به فایل IngredientForm.js می رویم و loadingIndicator را import می کنیم:

import LoadingIndicator from '../UI/LoadingIndicator';

همانطور که در ابتدای این فصل توضیح دادم LoadingIndicator یک کامپوننت spinner است که از قبل برایتان آماده کرده ام. اکنون در قسمت JSX همین فایل در صورتی که loading روی true باشد می خواهیم این Spinner را نشان دهیم:

// بقیه کد ها //
</div >
    <div className="ingredient-form__actions">
        <button type="submit">Add Ingredient</button>
        {props.loading ? <LoadingIndicator /> : null}
    </div>
</form >
</Card >
</section >

البته می توانیم از روش خلاصه نیز برای نوشتن کد بالا استفاده کنیم:

</div >
    <div className="ingredient-form__actions">
        <button type="submit">Add Ingredient</button>
        {props.loading && <LoadingIndicator />}
    </div>
</form >
</Card >
</section >

این دو کد دقیقا معادل هم هستند. در ضمن، من این کد را کنار button قرار داده ام تا زمانی که کاربر روی دکمه ثبت کلیک کرد، spinner در کنار دکمه نمایش داده شود. می توانید آن را در مرورگر خودتان تست کنید.

به فایل Ingredients.js برگردید و setIsLoading را در removeIngredientHandler نیز تنظیم کنید:

  const removeIngredientHandler = ingredientId => {
    setIsLoading(true);
    fetch(
      `https://react-hooks-update.firebaseio.com/ingredients/${ingredientId}.jon`,
      {
        method: 'DELETE'
      }
    ).then(response => {
      setIsLoading(false);
      setUserIngredients(prevIngredients =>
        prevIngredients.filter(ingredient => ingredient.id !== ingredientId)
      );
    });
  };

حالا می توانید کد بالا را در مرورگر خود تست کنید و بدون مشکل spinner را ببینید. در مرحله بعد باید خطاها را مدیریت کنیم. در حال حاضر اگر url درخواست بالا را تغییر دهیم تا به خطا برخورد کنیم تمام برنامه ما بهم می ریزد: spinner هیچ وقت قطع نمی شود، در سربرگ network یک درخواست 404 خواهیم داشت و در console مرورگر نیز چندین خطا دریافت می کنیم. بنابراین حتما باید این موارد را مدیریت کنیم. برای مدیریت خطاها از همان catch استفاده می کنیم اما ابتدا باید modal خطاهایی که به صورت آماده در پروژه به شما داده ام را وارد این فایل (Ingredients.js) کنیم:

import ErrorModal from '../UI/ErrorModal';

سپس یک state دیگر برای مدیریت آن تعریف می کنیم:

const Ingredients = () => {
  const [userIngredients, setUserIngredients] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState();
// بقیه کد ها //

من useState را خالی می گذارم چرا که در ابتدای کار هیچ خطایی نداریم. حالا می توانیم درون removeIngredientHandler از catch استفاده کنیم:

  const removeIngredientHandler = ingredientId => {
    setIsLoading(true);
    fetch(
      `https://react-hooks-update.firebaseio.com/ingredients/${ingredientId}.jon`,
      {
        method: 'DELETE'
      }
    ).then(response => {
      setIsLoading(false);
      setUserIngredients(prevIngredients =>
        prevIngredients.filter(ingredient => ingredient.id !== ingredientId)
      );
    }).catch(error => {
      setError('Something went wrong!');
      setIsLoading(false);
    });
  };

حالا در قسمت JSX می گوییم اگر خطایی وجود داشته باشد باید errorModal را نمایش بدهیم:

return (
    <div className="App">
      {error && <ErrorModal>{error}</ErrorModal>}

      <IngredientForm
        onAddIngredient={addIngredientHandler}
        loading={isLoading}
      />
// بقیه کدها //

همانطور که می بینید به error modal خودم پیام خطا (Error) را پاس داده ام تا به کاربر نمایش داده شود. البته اگر به ErrorModal.js بروید متوجه می شوید که این کامپوننت یک prop نیاز دارد که مسئول دریافت کلیک کاربر و بستن modal است، بنابراین این prop را نیز به آن پاس می دهیم:

{error && <ErrorModal onClose={clearError}>{error}</ErrorModal>}

حالا باید تابع clearError را تعریف کنیم که قطعا خارج از قسمت JSX است:

  const clearError = () => {
    setError(null);
  }

  return (
    <div className="App">
      {error && <ErrorModal onClose={clearError}>{error}</ErrorModal>}
// بقیه کد ها //

همانطور که می بینید این تابع مسئول حذف کردن خطاهای ما در State است تا modal بسته شود. حالا اگر با همان آدرس غلط برای fetch کد ها را ذخیره کنیم و صفحه را refresh کنیم، modal خود را می بینیم:

modal خطا به درستی کار می کند
modal خطا به درستی کار می کند

بدین صورت مدیریت خطاها نیز تکمیل می شود.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.