Przejdź do głównej zawartości

3. Testowanie

Jako programiści nie jesteśmy święci i często się mylimy, dlatego tutaj są spisane częste pomyłki przy pisaniu kodu oraz jak można nim zaradzić.

Używanie useEffect tam gdzie nie trzeba

useEffect służy do synchronizowania stanu z zewnętrznym systemem (ref. dokumentacja reacta), jeśli w useEffecie nie macie odwołania do zewnętrznego systemu (localStorage, api serwera, inne api przeglądarki) to znaczy, że go źle użyliście, przykładowy zły kod:

function handleMuscleClick(muscle: string) {
setSelectedMuscles((prevSelectedMuscles) => {
if (prevSelectedMuscles.includes(muscle)) {
return prevSelectedMuscles.filter((m) => m !== muscle);
} else {
return [...prevSelectedMuscles, muscle];
}
});
}
useEffect(() => {
table.getColumn("targetMuscle")?.setFilterValue(selectedMuscles);
}, [selectedMuscles, table]);

Tutaj synchronizujemy selectedMuscles z filtrowana wartościa z tabelce, tabelka żyje w świecie reacta i nie jest zewnętrznym systemem i równie dobrze możemy to zrobić w ten sposób:

function handleMuscleClick(muscle: string) {
const newSelectedMuscles = selectedMuscles.includes(muscle)
? prevSelectedMuscles.filter((m) => m !== muscle)
: [...prevSelectedMuscles, muscle];
table.getColumn("targetMuscle")?.setFilterValue(newSelectedMuscles);
setSelectedMuscles(newSelectedMuscles);
}

Teraz jest bardzo jasne, że po kliknięciu na mięsień, zostaje też zupdate’owana tabelka.

Ten przykład jeszcze nie jest taki zły, przy 1 useEffectcie dość łatwo się połapać co się dzieje, przy 11 robi się już kłopot i raz u mnie w pracy przez coś takiego padła produkcja 😭

Nieużywanie lub nadużywanie useMemo

Hook useMemo pozwala na zapamiętanie wyniku funkcji obliczeniowej między renderami komponentu, co może pomóc w optymalizacji wydajności aplikacji. Jest używany głównie w przypadku “drogich” obliczeń (obliczenia, które swoją złożonością mają istotny wpływ na wydajność), które nie muszą być wykonywane na nowo przy każdym renderze.

Składnia:

const memoizedValue = useMemo(() => computeValue(a, b), [a, b]);
  • computeValue: Funkcja zwracająca wynik obliczeń.
  • [a, b]: useMemo ponownie obliczy wartość tylko wtedy, gdy jedna z zależności w tej tablicy się zmieni.

Kiedy używać useMemo?

  • Przy “kosztownych” obliczeniach, takich jak złożone przekształcenia dużych ilości danych.
  • Kiedy wynik obliczeń jest wielokrotnie używany w danym renderze (np. w innych hookach).
  • Przy obliczeniach, które zależą od dynamicznie zmieniających się danych.

Kiedy nie używać useMemo?

  • Jeśli obliczenia są szybkie i nie mają zauważalnego wpływu na wydajność.
  • Gdy dodanie useMemo bardziej komplikuje kod niż przynosi korzyści.
  • Gdy zależności często się zmieniają, co może prowadzić do częstych ponownych obliczeń.

Co to za błąd hydracji w React/Next.js?

Jeśli używaliście SSR (server-side-rendering) z hydracją po stronie klienta (czyli możliwość zachowania reaktywności strony przy jak największym renderowaniu po stronie serwera), to może napotkaliście się z błędem w stylu Hydration failed:

Next.js hydration failed error

Dzieje się to, gdy jakakolwiek część DOM-u ma inną wartość po stronie serwera niż po stronie klienta. Innymi słowy — wtedy, kiedy używacie kodu niedeterministycznego względem okresu, w jakim zachodzi hydracja.

Przykładami sytuacji, które mogłoby to wywołać to używanie dat, np przy countdownie lub używanie wartości losowo wygenerowanych.

Sugerowane podejścia
  1. Renderowanie niedeterministycznych wartości wyłącznie po stronie klienta Zanim nastąpi hydracja, zwróć wersję komponentu bez treści dynamicznej. Gdy komponent zostanie w pełni załadowany, użyj useEffect aby zaktualizować komponent, co dzieje się po stronie klienta.

    function HydratedComponent() {
    const [isLoaded, setIsLoaded] = useState(false);
    const [currentDate, setCurrentDate] = useState(new Date());
    useEffect(() => {
    setIsLoaded(true);
    setCurrentDate(new Date());
    }, []);
    if (!isLoaded) return "Loading...";
    return currentDate.toLocaleDateString();
    }
  2. Tymczasowe zaokrąglanie wartości, aby w czasie hydracji nie zmienaiały się W przypadku dat, można użyć przybliżenia w takim stopniu, żeby w przewidywanym czasie załadowania strony nie uległo ono zmianie.

    /** Rounds the current date to the nearest `accuracy` milliseconds. */
    const getRoundedDate = (accuracy = 60000) =>
    new Date(Math.round(new Date().getTime() / accuracy) * accuracy);
    function HydratedComponent() {
    const [currentDate, setCurrentDate] = useState(getRoundedDate());
    return currentDate.toLocaleString();
    }

Metoda #2 działa w przypadku wartości, które można w jakimś stopniu zaokrąglać lub czynić mniej dokładnymi. W związku z tym, dla wartości losowo wygenerowanych (np. Math.random()), zaleca się stosowanie metody #1.