Let's think about it this way for a second
The dependency array in useEffect(callback, dependencyArray) controls when the effect should re-run — [] (empty) means it runs once on mount, [value] means it runs whenever value changes, and leaving the dependency array out entirely means it runs on every render (which can be risky). The cleanup function (the return () => {...} inside useEffect) runs when the component unmounts or before the dependency changes — useful for clearing timers or canceling subscriptions.
Let's connect this to a real-world scenario
On a search screen, if the API call fires immediately every time the searchQuery state changes (with no debounce), a request can go out on every single keystroke — implementing a debounce with setTimeout, and calling clearTimeout inside the cleanup function, prevents unnecessary requests while the user is still typing.
Code Example
import { useState, useEffect } from 'react';
function SearchScreen() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
if (!query) return;
const timeoutId = setTimeout(() => {
fetch(`https://api.example.com/search?q=${query}`)
.then((res) => res.json())
.then(setResults);
}, 500); // 500ms debounce
return () => clearTimeout(timeoutId); // cleanup — user ဆက်ရိုက်နေရင် timer အဟောင်းကို clear
}, [query]);
// ...
}While the user is typing, the request doesn't fire right away — it holds off, and only after 500ms with no typing does the API call run, just once.Try it in 5 minutes
Implement a debounce pattern (setTimeout + cleanup) yourself on a search screen.
A quick word of caution
Updating state inside useEffect and then including that same state in the dependency array can cause an infinite loop — choose your dependencies carefully so they're precise and necessary.