From 8fc85e415f249b5c2782cc3b88e48be2597376fb Mon Sep 17 00:00:00 2001
From: dm1sh <me@dmitriy.icu>
Date: Tue, 11 Jul 2023 14:43:08 +0300
Subject: [PATCH] Added component unmount cleanup

---
 front/src/hooks/api/useAddAnnouncement.js | 31 ++++++++++++++++++++---
 front/src/hooks/api/useBook.js            |  2 +-
 front/src/hooks/api/useFetch.js           | 23 ++++++++++++++---
 front/src/utils/index.js                  |  6 +++--
 4 files changed, 51 insertions(+), 11 deletions(-)

diff --git a/front/src/hooks/api/useAddAnnouncement.js b/front/src/hooks/api/useAddAnnouncement.js
index 8217ead..0b7f9d4 100644
--- a/front/src/hooks/api/useAddAnnouncement.js
+++ b/front/src/hooks/api/useAddAnnouncement.js
@@ -1,15 +1,30 @@
-import { useState } from "react"
+import { useEffect, useRef, useState } from "react"
 import { API_URL } from "../../config"
 
 const useAddAnnouncement = () => {
     const [status, setStatus] = useState("Опубликовать")
 
+    const timerIdRef = useRef()
+    const abortControllerRef = useRef()
+
     const doAdd = async (formData) => {
-        setStatus(true)
+        if (status === "Загрузка") {
+            abortControllerRef.current?.abort()
+            setStatus("Отменено")
+            timerIdRef.current = setTimeout(() => setStatus("Опубликовать"), 3000)
+            return
+        }
+
+        setStatus("Загрузка")
+
+        const abortController = new AbortController()
+        abortControllerRef.current = abortController
+
         try {
             const res = await fetch(API_URL + "/announcement", {
                 method: 'PUT',
                 body: formData,
+                signal: abortController.signal
             })
 
             const data = await res.json()
@@ -21,9 +36,17 @@ const useAddAnnouncement = () => {
 
         } catch (err) {
             setStatus(err.message ?? err)
-            setTimeout(() => setStatus("Опубликовать"), 10000)
+            timerIdRef.current = setTimeout(() => setStatus("Опубликовать"), 10000)
         }
-    } 
+    }
+
+    useEffect(() => {
+        const abortController = abortControllerRef.current
+        return () => {
+            clearTimeout(timerIdRef.current)
+            abortController?.abort()
+        }
+    })
 
     return {doAdd, status}
 }
diff --git a/front/src/hooks/api/useBook.js b/front/src/hooks/api/useBook.js
index e7dc9e5..37c38df 100644
--- a/front/src/hooks/api/useBook.js
+++ b/front/src/hooks/api/useBook.js
@@ -10,7 +10,7 @@ function useBook(id) {
     const [status, setStatus] = useState('')
 
     const handleBook = () => {
-        const token = getToken() || "Test"
+        const token = getToken()
 
         if (token) {
             setStatus("Загрузка")
diff --git a/front/src/hooks/api/useFetch.js b/front/src/hooks/api/useFetch.js
index a6c67e5..ab1997e 100644
--- a/front/src/hooks/api/useFetch.js
+++ b/front/src/hooks/api/useFetch.js
@@ -1,12 +1,22 @@
-import { useEffect, useState } from "react"
+import { useEffect, useRef, useState } from "react"
+import { isAborted } from '../../utils'
 
 const useFetch = (url, params, initialData) => {
     const [data, setData] = useState(initialData)
     const [loading, setLoading] = useState(true)
     const [error, setError] = useState("")
 
+    const abortControllerRef = useRef(null)
+
     useEffect(() => {
-        fetch(url, params)
+        if (abortControllerRef.current) {
+            abortControllerRef.current.abort()
+        }
+
+        const abortController = new AbortController()
+        abortControllerRef.current = abortController
+
+        fetch(url, { ...params, signal: abortControllerRef.current.signal })
             .then(res => {
                 if (!res.ok) {
                     switch (res.status) {
@@ -27,16 +37,21 @@ const useFetch = (url, params, initialData) => {
                 setLoading(false)
             })
             .catch(err => {
-                setError("Ошибка сети")
+                if (!isAborted(err)) {
+                    setError("Ошибка сети")
+                }
+
                 setLoading(false)
 
                 if (import.meta.env.DEV) {
                     console.log(url, params, err)
                 }
             })
+
+        return () => abortControllerRef.current.abort()
     }, [url, params])
 
-    return { data, loading, error }
+    return { data, loading, error, abort: abortControllerRef.current?.abort }
 }
 
 export default useFetch
diff --git a/front/src/utils/index.js b/front/src/utils/index.js
index a5f048e..f24284b 100644
--- a/front/src/utils/index.js
+++ b/front/src/utils/index.js
@@ -5,6 +5,8 @@ const removeNull = (obj) => Object.fromEntries(
             key,
             value === Object(value) ? removeNull(value) : value,
         ]),
-);
+)
 
-export { removeNull }
\ No newline at end of file
+const isAborted = (err) => err.name == 'AbortError'
+
+export { removeNull, isAborted }
\ No newline at end of file