Strona główna / Artykuły / Przestań automatycznie używać useEffect do rozwiązywania każdego problemu w React.

Przestań automatycznie używać useEffect do rozwiązywania każdego problemu w React.

Efekty synchronizują się z zewnętrznymi systemami; generowane są podczas renderowania, obsługują kliknięcia w zdarzeniach, a dane pobierane są za pomocą narzędzi do pracy z rzeczywistymi danymi.

1424 słów

Pułapka useEffect

Osoby uczące się hooków często korzystają z useEffect do pobierania danych, tworzenia stanu pochodnego oraz obsługi kliknięć przycisków. Efekty te synchronizują się z zewnętrznymi systemami — nie są standardowym narzędziem do zwykłej logiki renderowania.

Czym właściwie jest useEffect

Po zakończeniu procesu renderowania React uruchamia efekty w celu dołączenia słuchaczy, nawiązania połączeń lub bezpośredniej komunikacji z czymś poza Reactem. Niewłaściwe użycie powoduje problemy takie jak migotanie, podwójne pobieranie danych oraz błędy związane z konkurencją.

// ❌ BAD: Two renders, unnecessary state
function UserProfile({ firstName, lastName }) {
  const [fullName, setFullName] = useState('');
useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
  }, [firstName, lastName]);
  return <h1>{fullName}</h1>;
}
// ✅ GOOD: Calculate during render
function UserProfile({ firstName, lastName }) {
  const fullName = `${firstName} ${lastName}`;
return <h1>{fullName}</h1>;
}
// ❌ BAD: Unnecessary effect + state
function ProductList({ products }) {
  const [sortedProducts, setSortedProducts] = useState([]);
useEffect(() => {
    const sorted = [...products].sort((a, b) => a.price - b.price);
    setSortedProducts(sorted);
  }, [products]);
  return (
    <ul>
      {sortedProducts.map(product => (
        <li key={product.id}>{product.name} - ${product.price}</li>
      ))}
    </ul>
  );
}
// ✅ GOOD: Calculate during render (useMemo for expensive ops)
function ProductList({ products }) {
  const sortedProducts = useMemo(() => {
    return [...products].sort((a, b) => a.price - b.price);
  }, [products]);
return (
    <ul>
      {sortedProducts.map(product => (
        <li key={product.id}>{product.name} - ${product.price}</li>
      ))}
    </ul>
  );
}
// ❌ BAD: Manual fetching with useEffect
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
useEffect(() => {
    let cancelled = false;
    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('Failed to fetch');
        const data = await response.json();
        if (!cancelled) setUser(data);
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    }
    fetchUser();
    return () => { cancelled = true; };
  }, [userId]);
  if (loading) return <Spinner />;
  if (error) return <p>Error: {error}</p>;
  return <div>{user.name}</div>;
}
// ✅ GOOD: Use TanStack Query (React Query)
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
  const { data: user, isPending, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
  });
  if (isPending) return <Spinner />;
  if (error) return <p>Error loading user</p>;
  return <div>{user.name}</div>;
}
// ❌ BAD: Overcomplicating with useEffect
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
useEffect(() => {
    if (liked) {
      fetch(`/api/posts/${postId}/like`, { method: 'POST' });
    } else {
      fetch(`/api/posts/${postId}/unlike`, { method: 'POST' });
    }
  }, [liked, postId]);
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}
// ✅ GOOD: Handle in the event handler
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
const handleLike = async () => {
    const newLiked = !liked;
    setLiked(newLiked);
    await fetch(`/api/posts/${postId}/${newLiked ? 'like' : 'unlike'}`, {
      method: 'POST',
    });
  };
  return (
    <button onClick={handleLike}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}
function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);

    // Cleanup is crucial!
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  return <p>Window width: {width}px</p>;
}
function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
useEffect(() => {
    const connection = createWebSocketConnection(roomId);

    connection.onMessage = (msg) => {
      setMessages(prev => [...prev, msg]);
    };
    connection.connect();
    return () => {
      connection.disconnect(); // Cleanup!
    };
  }, [roomId]);
  return <MessageList messages={messages} />;
}
function ChartComponent({ data }) {
  const chartRef = useRef(null);
  const chartInstance = useRef(null);
useEffect(() => {
    if (chartRef.current && !chartInstance.current) {
      chartInstance.current = new ChartLibrary(chartRef.current, {
        type: 'line',
        data: data,
      });
    }
    return () => {
      chartInstance.current?.destroy();
      chartInstance.current = null;
    };
  }, []);
  return <canvas ref={chartRef} />;
}
function PageTitle({ title }) {
  useEffect(() => {
    document.title = `${title} | My App`;

    return () => {
      document.title = 'My App'; // Reset on unmount
    };
  }, [title]);
return null;
}

Cztery powszechne błędy

1. Obliczanie wartości w efektach

Obliczaj podczas renderowania (lub za pomocą useMemo, gdy operacja jest kosztowna). Efekty, które używają setState na podstawie propsów/stanu, często zawierają zabronione pętle sprzężenia zwrotnego.

2. Kosztowne obliczenia w efektach

Pure obliczenia należy wykonywać podczas renderowania/memozacji, a nie po narysowaniu elementów.

3. Ładowanie danych jako niekontrolowany efekt

Proces ładowania wymaga możliwości anulowania, cache’owania oraz odpowiedniego radzenia sobie z błędami. Lepiej używać dedykowanych bibliotek lub frameworków z mechanizmami obsługi ładowania; jeśli korzystasz z efektów, musisz zarządzać sytuacjami przerwania i konfliktów.

4. Działania użytkownika w efektach

Obsługa kliknięć powinna znajdować się w obsługach zdarzeń. Efekty, które uruchamiają się po zmianie stanu spowodowanej kliknięciem, wprowadzają pośredniość i błędy.

Drzewo decyzyjne

Jeśli obliczenia opierają się na istniejącym stanie/propsach → renderowanie. Jeśli reagują na kliknięcie/przesłanie danych → obsługa zdarzenia. Jeśli komunikują się z API DOM, źródłem danych sieciowym lub cyklem życia komponentu zewnętrznego → efekt.

Kiedy efekty są odpowiednie

Słuchacze zdarzeń zewnętrznych

WebSockets

Inicjalizacja bibliotek third-party

API niereaktywne, które muszą być czysto usuwane

Zakończenie

Mniej efektów, jaśniejszy przepływ danych. Zachowaj useEffect do synchronizacji z systemami poza Reactem — nie jako narzędzie domyślne do każdej operacji po renderowaniu.

Tryb Strict Mode uruchamia efekty dwukrotnie w fazie rozwoju, aby ujawnić brakujące operacje czyszczenia — traktuj to jako zaletę przy łączeniu sokietów. Woląj deklaratywne biblioteki danych do zarządzania stanem serwera, aby komponenty opisywały zależności zamiast ręcznie tworzyć efekty fetch. Przed dodawaniem efektów, które jedynie kopiują właściwości do stanu, dokonaj oceny; taki wzorzec to zazwyczaj nieporozumienie dotyczące procesu renderowania.

Funkcje czyszczenia muszą usuwać słuchacze i timerzy; pozostawione w tle obsługi są klasycznym źródłem ostrzeżeń o użyciu setState po odmontowaniu komponentu.

Mассивy zależności są częścią umowy — jeśli pominie się wartości, pojawiają się przestarzałe zamknięcia; nadmierna lista obiektów i efektów powoduje spowolnienie przy każdym renderowaniu.

Podczas synchronizacji z parametrami wyszukiwania w URL lub zewnętrznymi magazynami należy preferować specjalnie stworzone hooki, które zamykają obszar działania efektów.

Aplikacje renderowane na serwerze nie powinny uruchamiać efektów dostępnych tylko w przeglądarce bez zabezpieczeń; należy to kontrolować na poziomie środowiska lub cyklu życia aplikacji.

Funkcje czyszczenia muszą usuwać słuchacze i timery; pozostawione bez nadzoru obsługi są typowym źródłem ostrzeżeń o zmianie stanu po odmontowaniu komponentu.

Mассивy zależności są częścią umowy — jeśli pominie się wartości, pojawiają się przestarzałe zamknięcia; nadmierna lista obiektów i efektów powoduje spowolnienie przy każdym renderowaniu.

Podczas synchronizacji z parametrami wyszukiwania w URL lub zewnętrznymi magazynami należy preferować specjalnie stworzone hooki, które zamykają obszar działania efektów.

Aplikacje renderowane na serwerze nie powinny uruchamiać efektów dostępnych tylko w przeglądarce bez zabezpieczeń; kontroluj to na poziomie środowiska lub cyklu życia aplikacji.

Funkcje czyszczące muszą usuwać słuchacze i timery; pozostawione w tle obsługi są typowym źródłem ostrzeżeń o zmianie stanu po odmontowaniu aplikacji.

Tablice zależności są częścią umowy — jeśli pominie się odpowiednie wartości, pojawią się przestarzałe zamykania funkcji; nadmierna lista obiektów i efektów powoduje problemy przy każdym renderowaniu.

Priorytetowo używaj specjalnie stworzonych hooków do synchronizacji z parametrami wyszukiwania w URL lub zewnętrznymi źródłami danych, które izolują obszar działania tych efektów.

Aplikacje renderowane na serwerze nie powinny uruchamiać efektów dostępnych tylko w przeglądarce bez zabezpieczeń; kontroluj to na poziomie środowiska lub cyklu życia aplikacji.

Funkcje czyszczące muszą usuwać słuchacze i timery; pozostawione w tle obsługi są typowym źródłem ostrzeżeń o zmianie stanu po odmontowaniu aplikacji.

Mассивy zależności są częścią umowy — jeśli pominie się wartości, pojawiają się przestarzałe zamknięcia; nadmierna lista obiektów i efektów powoduje spowolnienie przy każdym renderowaniu.

Podczas synchronizacji z parametrami wyszukiwania w URL lub zewnętrznymi magazynami należy preferować specjalnie stworzone hooki, które zamykają obszar działania efektów.

Aplikacje renderowane na serwerze nie powinny uruchamiać efektów dostępnych tylko w przeglądarce bez zabezpieczeń; należy to kontrolować na poziomie środowiska lub cyklu życia aplikacji.

Funkcje czyszczenia muszą usuwać słuchacze i timery; pozostawione bez nadzoru obsługi są typowym źródłem ostrzeżeń o zmianie stanu po odmontowaniu komponentu.

Mассивy zależności są częścią umowy — jeśli pominie się wartości, pojawiają się przestarzałe zamknięcia; nadmierna lista obiektów i efektów powoduje spowolnienie przy każdym renderowaniu.

Podczas synchronizacji z parametrami wyszukiwania w URL lub zewnętrznymi magazynami należy preferować specjalnie stworzone hooki, które zamykają obszar działania efektów.

Aplikacje renderowane na serwerze nie powinny uruchamiać efektów dostępnych tylko w przeglądarce bez zabezpieczeń; kontroluj to na poziomie środowiska lub cyklu życia aplikacji.

Funkcje czyszczące muszą usuwać słuchacze i timery; pozostawione w tle obsługi są typowym źródłem ostrzeżeń o zmianie stanu po odmontowaniu aplikacji.

Tablice zależności są częścią umowy — jeśli pominie się odpowiednie wartości, pojawią się przestarzałe zamykania funkcji; nadmierna lista obiektów i efektów powoduje problemy przy każdym renderowaniu.

Priorytetowo używaj specjalnie stworzonych hooków do synchronizacji z parametrami wyszukiwania w URL lub zewnętrznymi źródłami danych, które izolują obszar działania tych efektów.

Aplikacje renderowane na serwerze nie powinny uruchamiać efektów dostępnych tylko w przeglądarce bez zabezpieczeń; kontroluj to na poziomie środowiska lub cyklu życia aplikacji.

Funkcje czyszczące muszą usuwać słuchacze i timery; pozostawione w tle obsługi są typowym źródłem ostrzeżeń o zmianie stanu po odmontowaniu aplikacji.

Tablice zależności są częścią umowy — jeśli pominie się wartości, pojawiają się przestarzałe zamknięcia; nadmierna lista obiektów powoduje problemy przy każdym renderowaniu.

Priorytetowo należy używać specjalnie stworzonych hooków do synchronizacji z parametrami wyszukiwania w URL lub zewnętrznymi magazynami danych, które izolują obszar wpływu tych działań.

Aplikacje renderowane na serwerze nie powinny uruchamiać efektów dostępnych tylko w przeglądarce bez odpowiednich zabezpieczeń; należy to kontrolować na poziomie środowiska lub cyklu życia aplikacji.

Funkcje czyszczące muszą usuwać słuchacze i timerzy; pozostawione bez nadzoru obsługi są typowym źródłem ostrzeżeń o zmianie stanu po odmontowaniu komponentu.