Kolejny przykład asynchronicznego useEffect z internetu, omawiamy go sobie, kontynuacja lekcji poprzedniej. Do dzieła.
Najpierw użycie tego hooka:
export default function App() {
const [query, setQuery] = useState("");
const [selectedId, setSelectedId] = useState(null);
const { movies, isLoading, error } = useMovies(query);
Czyli widzimy, że:
- query to stan RFC App
- to query jest przekazywane do useMovies, jest jego zależnością
- selectedId nas nie interesuje, jest potrzebne w App do czego innego
- Hook zwraca movies, isLoading oraz error
Ok, jak on wygląda:
import { useState, useEffect } from "react";
const KEY = "f84fc31d";
Na początku importy, oraz stała key, powyżej RFC lub funkcji hook w tym wypadku, zatem stała, współdzielona wewnątrz pliku, jako const o typie prostym niezmienna.
Teraz sam hook:
export function useMovies(query) {
const [movies, setMovies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
Już widać, że wszystko, co zwracamy to są obiekty state. Po prostu zarządzanie tymi stanami wywaliliśmy do reużywalnego custom hooka.
Dalej mamy useEffect:
export function useMovies(query) {
const [movies, setMovies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
useEffect(
function () {
const controller = new AbortController();
async function fetchMovies() {
//(...)
}
fetchMovies();
return function () {
controller.abort();
};
},
[query]
);
return { movies, isLoading, error };
}
AbortController powyżej poziomu definicji async funkcji fetchmovies, jej definicja, wywołanie, cleanup używający aborta, query w talicy zależności.
Teraz zobaczmy definicję tej funkcji:
async function fetchMovies() {
try {
setIsLoading(true);
setError("");
const res = await fetch(
`http://www.omdbapi.com/?apikey=${KEY}&s=${query}`,
{ signal: controller.signal }
);
if (!res.ok)
throw new Error("Something went wrong with fetching movies");
const data = await res.json();
if (data.Response === "False") throw new Error("Movie not found");
setMovies(data.Search);
setError("");
} catch (err) {
if (err.name !== "AbortError") {
console.log(err.message);
setError(err.message);
}
} finally {
setIsLoading(false);
}
}
Nic trudnego. Mamy też swego rodzaju optymalizację przed wywołaniem fetchMovies, poprzednio ją przeoczyłem, trochę przypadkiem, ale też bez sensu było przytłaczać tym kodem.
Oto optymalizacja:
if (query.length < 3) {
setMovies([]);
setError("");
return;
}
fetchMovies();
return function () {
controller.abort();
};
},
[query]
);
return { movies, isLoading, error };
}
Czyli nie wysyłamy requestów przesadnie dużo. Ok, to by było na tyle, temat będziemy kontynuować.