React components render based on props and state โ but real apps also need to interact with things React doesn't manage directly: setInterval timers, browser APIs like window.addEventListener, or a network request to a server. useEffect is the tool for connecting a component to these external systems.
import { useEffect, useState } from "react";
function Clock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(timer); // cleanup function
}, []); // empty array - run once on mount
return <p>{time.toLocaleTimeString()}</p>;
}- No array at all: runs after every single render (rarely what you want)
[]empty array: runs exactly once, when the component first mounts[someValue]: runs once on mount, then again any timesomeValuechanges
Returning a function from inside useEffect tells React to run it right before the effect runs again, or when the component unmounts entirely. In the clock example above, clearInterval(timer) stops the old timer before starting a fresh one โ without this cleanup, you'd accumulate multiple overlapping timers every time the effect re-ran, a very common source of subtle bugs and memory leaks.
If an effect uses a variable from component scope (like a prop or state value) but that variable isn't listed in the dependency array, the effect can run with a stale, outdated value โ a bug that's notoriously hard to track down. React's ESLint plugin will warn about this; when in doubt, include every value the effect actually reads.
Build a component that subscribes to the browser's online/offline status using window event listeners inside useEffect, with proper cleanup.
import { useState, useEffect } from "react";
function NetworkStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
function goOnline() { setIsOnline(true); }
function goOffline() { setIsOnline(false); }
window.addEventListener("online", goOnline);
window.addEventListener("offline", goOffline);
return () => {
window.removeEventListener("online", goOnline);
window.removeEventListener("offline", goOffline);
};
}, []);
return <p>{isOnline ? "๐ข Online" : "๐ด Offline"}</p>;
}
Q Do I need useEffect for every side effect?
No โ useEffect is specifically for synchronizing with something outside React (timers, subscriptions, network requests). Simple event handlers, like a button's onClick, don't need useEffect at all.
Q Why does my effect run twice in development mode?
In React 18's StrictMode (used by default in new Vite projects), effects intentionally run twice in development only, to help you catch missing cleanup functions. This doesn't happen in production builds.