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.
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.