In Part 1 of “Let’s Master React Hooks Together” we focused on useState — how React remembers things, updates UI, and keeps everything in sync.
You learned how to control your app.
But then comes the next question:
What happens when your app needs to talk to the outside world?
You open Instagram, blink once… and boom — your feed is alive.
Posts load. Stories appear. Notifications sneak in. Everything feels instant.
But here’s what most beginners don’t realize:
👉 React didn’t do that.
👉 React only rendered the empty screen.
Everything after that — fetching data, syncing with APIs, setting up listeners…
That’s where useEffect steps in.
This is where React stops being just about state…
and starts becoming interactive with the real world.
And once you truly understand this hook:
You stop just managing state… and start thinking like React.
So what is useEffect… really?
Let’s skip the robotic definition for a second.
👉 useEffect lets your component do things after it renders.
Now the official version (just so you’re covered):
“The Effect Hook lets you perform side effects in function components.”
Cool. But what does that actually mean?
“Side Effects” — sounds scary, isn’t.
Relax. It’s simpler than it sounds.
A side effect is anything your component does that is not just returning UI.
Think about real apps:
- Fetching data from a server
- Updating the page title
- Listening to scroll or clicks
- Running a timer
- Saving something in local storage
👉 These don’t directly render JSX — but they make your app feel alive.
Why does useEffect even exist?
Because React is strict about one thing:
Rendering must stay pure.
That means:
- Same input → same UI output
- No hidden behavior
- No randomness during render
But real apps? Total chaos
Let’s bring back the Instagram example:
- Open app → fetch posts
- Like a post → send data to server
- Scroll → load more content
👉 None of this is “rendering UI.”
So React needed a safe place for this “messy” stuff.
And it basically said:
“Do whatever you want… just don’t do it during render.”
That safe place = useEffect.
The exact problem it solves (this is the aha moment)
Imagine writing this:
function Feed() {
const data = fetchPosts(); // ❌ dangerous
return <div>{data}</div>;
}
Looks innocent. But here’s what really happens:
→ Component renders
→ fetchPosts() runs
→ Data changes → triggers render
→ It runs again
→ And again…
You’ve just created a loop.
Now watch the same thing done properly:
function Feed() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetchPosts().then(setPosts);
}, []);
return <div>{posts.length} posts</div>;
}
First render → UI shows empty state
Then useEffect runs → fetch starts
Data arrives → state updates
Component re-renders → UI updates
👉 No chaos. No loops. Just control.
The mental model that changes everything
Forget technical jargon. Think like this:
“What should happen after the screen updates?”
That answer = your useEffect.
How to actually use useEffect
Here’s the shape:
useEffect(() => {
// effect logic
return () => {
// cleanup logic
};
}, [dependencies]);
Now let’s feel what each part does.
Effect running every time
useEffect(() => {
console.log("Rendering happened");
});
Render → effect runs
Render again → runs again
👉 No control. Just chaos checking everything.
Run only once (most common beginner case)
useEffect(() => {
console.log("Component loaded");
}, []);
Component appears → effect runs once
Never again
👉 Perfect for API calls, initial setup.
Run when something changes
useEffect(() => {
console.log("User changed");
}, [user]);
Initial render → runs
If user changes → runs again
If nothing changes → does nothing
👉 This is where real apps live.
Cleanup — the part most people ignore (big mistake)
useEffect(() => {
const interval = setInterval(() => {
console.log("Checking notifications...");
}, 3000);
return () => {
clearInterval(interval);
};
}, []);
Component loads → interval starts
Every 3 seconds → it runs
Now imagine leaving the page…
Without cleanup:
The interval keeps running
Multiple intervals stack
App slows down
With cleanup:
React removes the interval cleanly
Nothing leaks
Everything stays predictable
👉 Cleanup runs:
- Before the effect runs again
- When the component disappears
Lifecycle… but explained like a human
Forget the textbook words.
Here’s what actually happens:
🟢 Component shows up
→ UI renders
→ useEffect runs
🟡 Something changes
→ React updates UI
→ useEffect checks dependencies
→ Runs only if needed
🔴 Component goes away
→ Cleanup runs
That’s the whole story.
Where you should use useEffect
Use it when your app needs to:
✔ Fetch data (feeds, dashboards, profiles)
✔ Listen to events (scroll, resize, clicks)
✔ Sync with APIs or storage
✔ Run timers or intervals
✔ Handle subscriptions (live chats, notifications)
When NOT to use it (this saves hours of frustration)
Bad pattern:
useEffect(() => {
setFullName(first + last);
}, [first, last]);
This creates unnecessary re-renders.
Better:
const fullName = first + last;
Nothing async
No external system
No side effect
👉 Then you don’t need useEffect.
Real-world example (Instagram-style thinking)
function Stories({ userId }) {
const [stories, setStories] = useState([]);
useEffect(() => {
let active = true;
fetch(`/api/stories/${userId}`)
.then(res => res.json())
.then(data => {
if (active) setStories(data);
});
return () => {
active = false;
};
}, [userId]);
return (
<div>
{stories.map(story => (
<img key={story.id} src={story.image} />
))}
</div>
);
}
Component appears → empty stories render
Effect runs → fetch starts for current user
When data returns → it checks if component is still active
If yes → updates state → UI refreshes
Now if user switches quickly:
Cleanup runs → marks previous request inactive
Old data won’t overwrite new UI
👉 This is how real apps avoid subtle bugs.
The shift that makes you dangerous (in a good way)
Most beginners ask:
“How do I use
useEffect?”
But better developers ask:
“Should this even be an effect?”
That one question will level you up faster than anything else.
Next time you open an app…
Whether it’s Instagram, YouTube, or anything dynamic…
Ask yourself:
- What ran after the UI loaded?
- What triggered that update?
- What got cleaned up when I left?
That curiosity turns confusion into clarity.
Final takeaway
So in this second part of “Let’s Master React Hooks Together” you moved beyond useState…
…and stepped into useEffect.
Not just a hook — but a mindset shift.
Because useEffect is the bridge between:
React’s clean, predictable state world
The messy, asynchronous real world
When you understand that bridge:
You stop writing “just code.”
You start designing behavior.
You know when things should run.
You know why they run.
And that’s when React starts to feel less like magic…
and more like a tool you actually control.
In the next part, we’ll keep building on this — making your hooks cleaner, smarter, and more predictable.
Top comments (0)