Jest sobie funkcja wykonująca asynchroniczną operację pobierania danych, która jeszcze do niedawna gnieździła się spokojnie w podstawowym flow po naciśnięciu guzika, działała i nie wadziła nikomu. Niemniej poczytałam co nieco i wniosek był taki aby przenieść jej wywołanie do useEffect()
. OK. Niby jasne. I żeby w tym useEffect()
był zwracany cleanup. To w zasadzie też jasne choć nieco mniej (nie jarzyłam co taka funkcja konkretnie miałaby robić w wypadku fetcha a wiedza z timerów była nizbyt przydatna. Wreszcie zaskoczyło - fetch AbortController. Pytanie tylko czy uzywam go prawidłowo. Żródło, z którego czerpałam wiedzę to https://hackernoon.com/cleanup-functions-in-reacts-useeffect-hook-explained
niby fajne bo proste, ale pozostawia pewne wątpliwości.
Tak wygląda effect:
React.useEffect(() => {
let controller = new AbortController();
if (URL) {
fetchBooksFromAPI(URL, controller);
}
return () => controller?.abort();
}, [URL, fetchBooksFromAPI]);
A tak hook, gdzie tworzona jest funkcja fetchBooksFromAPI()
. Dla czytelności wywaliłam treści z kilku callbacków - one działają jak trzeba. Żeby było ciekawiej, tu jest rekursja, nie wiem a priori ile danych jest do pobrania, a ilość rekordów w jednorazowym fetchu ograniczona. Właściwą robotę robi funkcja recursiveFetch. Zaznaczyłam miejsca gdzie MOŻE zabicie powinno się znaleźć. Poza tym pytanie - czy to powinno być zabite przez controller = null, czy przez controller?.abort()? W artykule są użyte obie wersje, fakt, że w dwóch różnych kontekstach ale różnica jest dla mnie wątpliwa.
export const useFetchBooks = () => {
const navigate = useNavigate();
const showMessage = useMessage();
const { setIsLoading, showError, fetchBooks, setIsFromNetwork } = useDispatchAction();
const fetchBooksFromAPI = (path: string, controller: AbortController) => {
let startIndex = 0;
let foundBooks = INITIAL_FOUND_BOOKS;
let fetchSummary = INITIAL_FETCH_SUMMARY;
setIsLoading(true);
const handleError = (response: any) => {
};
const handleNotFound = () => {
};
const handleSuccess = (foundBooks: FoundBooks) => {
/// druga propozycja - zabić w tym miejscu. Ale jeżeli w tym to dlaczego nie we wszystkich callbackach kończących jakąś gałąź fetcha?
};
const increaseCounter = () => {
};
const addBooks = (moreBooks: any) => {
};
async function recursiveFetch() {
const fullPath = path + startIndex.toString();
const fetchResult = await fetch(fullPath, { signal: controller.signal }).catch(error => {
handleError(error);
});
if (fetchResult) {
const response = await fetchResult.json();
/// pierwsza propozycja - zabić w tym miejscu
const code = getValue(response, "code");
if (isErrorCode(code)) {
handleError(response);
return;
}
if (response.items) {
increaseCounter();
addBooks(response.items);
recursiveFetch();
} else {
if (response.error) {
handleError(response);
}
if (!response.eror && foundBooks.length === 0) {
handleNotFound();
} else {
handleSuccess(foundBooks);
}
}
}
}
recursiveFetch();
};
return React.useMemo(() => fetchBooksFromAPI, []); // musi być zmemoizowane, jeżeli ma się pojawić jako dependencja
};
Doprawdy, nie wiem jak należałoby to zrobić