Understanding useEffect: Side Effects in React (Tutorial)
Understanding useEffect
~11 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Welcome to Phase 2: we now dive deep into the most important hooks – functions that let components "hook into" React functionality like state (hence the name "hook"). You already know the first one, useState. Now comes useEffect – one of the most important, but also most misunderstood, hooks.
What is a "side effect"?
Rendering a component (running the function and returning JSX) should always be a "pure" affair: same props/state in, same JSX out, no side effects on the outside world. But some things can't be expressed that way: loading data from a server, changing the browser tab title, starting a timer, listening for a browser event. These are side effects – things that happen outside of pure rendering. That's what useEffect is for.
useEffect() basics
import { useEffect } from 'react';
function Example() {
useEffect(() => {
console.log('The component was rendered (or re-rendered)');
});
return <p>Hello</p>;
}Without a second argument, the effect function runs after EVERY render – usually not what you want. The second argument, the "dependency array", controls exactly WHEN the effect should run again:
| Call | When the effect runs |
|---|---|
useEffect(fn) – no second argument | runs after EVERY render (rarely useful) |
useEffect(fn, []) – empty array | runs only ONCE, right after the first render |
useEffect(fn, [value]) – array with values | runs again whenever one of the listed values changes |
Practical example: keeping the browser tab title in sync
A classic, real side effect: the browser tab title lives outside the React-controlled <div id="root"> and must be set via the browser API document.title. Extend src/App.jsx:
import { useEffect } from 'react';
import ProductCard from './components/ProductCard';
const SAMPLE_PRODUCTS = [
{ sku: 'boots-01', name: 'Hiking Boots', price: 89.99, imageUrl: 'https://picsum.photos/seed/1/300' },
{ sku: 'backpack-01', name: 'Trekking Backpack', price: 59.5, imageUrl: 'https://picsum.photos/seed/2/300' },
{ sku: 'jacket-01', name: 'Rain Jacket', price: 74.0, imageUrl: 'https://picsum.photos/seed/3/300' },
];
function App() {
useEffect(() => {
document.title = `Product Catalog (${SAMPLE_PRODUCTS.length} products)`;
}, []);
return (
<div className="app">
<h1>Product Catalog</h1>
<div className="product-grid">
{SAMPLE_PRODUCTS.map((product) => (
<ProductCard
key={product.sku}
name={product.name}
price={product.price}
imageUrl={product.imageUrl}
onSelect={() => alert(product.name + ' tapped')}
/>
))}
</div>
</div>
);
}
export default App;Save and look at the browser tab: "Product Catalog (3 products)". The empty array [] makes sure this code runs only ONCE, right after the component is first displayed – comparable to the DOMContentLoaded event.
The cleanup function
Some effects need to be "cleaned up" when the component disappears or the effect runs again – e.g. a timer that would otherwise keep running even though the component is long gone. For this, the effect function itself can return a function:
useEffect(() => {
const timerId = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(timerId); // runs before the effect re-runs OR the component disappears
};
}, []);Achtung: The most common useEffect mistake for beginners: forgetting the dependency array or filling it incorrectly. If a value the effect function actually uses is missing, the "react-hooks/exhaustive-deps" ESLint rule (pre-installed in every Vite React project) warns in your editor – take that warning seriously, it almost always points to a real bug (the effect then "sees" stale values).