DEV Community

Jalaj Bankar
Jalaj Bankar

Posted on

useEffect, Callbacks, and How They Work Together

Short session but genuinely important stuff. These two concepts — useEffect and callbacks — are everywhere in React and JavaScript. Understanding them properly changes how you read code.

useEffect — When Does It Actually Run?
Simple and worth stating clearly: useEffect runs after every render of the component it lives in. That's its default behaviour — component renders, effect fires. No magic, no mystery.
You control when it runs using the dependency array:
useEffect(() => { ... }, []); — runs only once, on first render.
useEffect(() => { ... }, [value]); — runs whenever value changes.
useEffect(() => { ... }); — runs after every single render, no restrictions.
That's the whole mental model. Render happens first, then the effect. Always in that order.


Callbacks — A Function That Waits Its Turn
A callback is just a function you hand to another function and say "you decide when to call this."
The flow is always the same: main function runs first → callback runs inside it, wherever it was placed.
function iAmGonnaBeCallback() {
console.log("I ran second!");
}
function runsInsideMe(callback) {
console.log("I ran first!");
callback();
}
runsInsideMe(iAmGonnaBeCallback);

You're not calling iAmGonnaBeCallback yourself — you're handing it over and letting runsInsideMe decide where it fires. Could be line 1, could be line 555. You don't control that. The parent does.
Three things worth locking in about callbacks:
The callback must be defined before it's passed in — JavaScript needs to know what it is before handing it over.
You pass the function without parentheses — runsInsideMe(iAmGonnaBeCallback) not runsInsideMe(iAmGonnaBeCallback()). Parentheses would call it immediately, not pass it.
It runs exactly where it's placed inside the parent — not before, not after.


How a Callback Saves Your App Inside useEffect
Here's the flow when useEffect handles an API call — and why the callback pattern matters here:
App opens → component renders → useEffect fires immediately after → if there's no callback structure, it tries to fetch data right away → but what if the component isn't ready yet, or you want more control over timing?
The clean pattern is to define your fetch function outside and pass it in — or define it inside the effect and call it properly:
useEffect(() => {
function fetchData() {
// API call here
}
fetchData();
}, []);

The effect runs, then calls your function inside it — exactly like the callback pattern. useEffect is the parent, your fetch function is the callback. It runs where you placed it, when the effect decides. Clean, predictable, no surprises.

Top comments (0)