useEffect runs side effects after the component renders. Effects are scheduled to run after browser paint, without blocking the UI.
Signature
function useEffect(
effect: () => void | (() => void),
inputs?: ReadonlyArray<unknown>
): void
Parameters
effect
() => void | (() => void)
required
A function containing the imperative, effectful code. Can optionally return a cleanup function that will be called before the effect runs again or when the component unmounts.
An array of dependencies. The effect will only re-run if one of the values in this array has changed (compared using ===). If omitted, the effect runs after every render.
Returns
void
Basic Usage
import { useEffect, useState } from 'preact/hooks';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
// Update document title after render
document.title = `Count: ${count}`;
}, [count]); // Only re-run when count changes
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
Effect with Cleanup
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// Cleanup function
return () => {
clearInterval(interval);
};
}, []); // Empty array = run once on mount
return <div>Seconds: {seconds}</div>;
}
Fetching Data
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function fetchUser() {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!cancelled) {
setUser(data);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchUser();
// Cleanup: prevent state updates if component unmounts
return () => {
cancelled = true;
};
}, [userId]); // Re-fetch when userId changes
if (loading) return <div>Loading...</div>;
return <div>{user?.name}</div>;
}
Event Listeners
function WindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
// Cleanup: remove listener on unmount
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // Empty deps = only setup/cleanup once
return <div>Window width: {width}px</div>;
}
Run Once on Mount
function InitialSetup() {
useEffect(() => {
console.log('Component mounted');
return () => {
console.log('Component will unmount');
};
}, []); // Empty dependency array
return <div>Hello</div>;
}
Multiple Effects
function MultipleEffects() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// Effect for count
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
// Effect for name
useEffect(() => {
localStorage.setItem('name', name);
}, [name]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<input value={name} onChange={e => setName(e.target.value)} />
</div>
);
}
Effects run after the browser has painted, making them suitable for most side effects. For effects that must run synchronously before paint (like measuring DOM layout), use useLayoutEffect instead.
Always include all values from the component scope that are used inside the effect in the dependency array. This ensures your effect always has the latest values.
If your effect returns a cleanup function, it will be called before running the effect again (when dependencies change) and when the component unmounts.