
When beginners start learning UI Full Stack web with React Hooks, almost everyone understands useState quickly but useEffect feels confusing.
Why does it run after the render?
Why does it depend on an array?
Why does it run multiple times?
Why does it trigger again when values change?
What are side effects anyway?
These questions are extremely common.
But once you understand what useEffect really does, it becomes one of the easiest and most powerful hooks in React.
This blog explains useEffect in the simplest possible way, using real-time, real-world examples not code and with absolute clarity.
useEffect is a React Hook that lets you run logic after React has rendered the UI.
This logic is usually something that:
React itself cannot do
must happen outside the rendering process
must occur because something changed
In technical terms:
useEffect handles "side effects" logic that lives outside the pure rendering flow.
When React renders the UI, it wants the process to be:
predictable
pure (only UI output)
consistent
fast
Anything that happens outside rendering is considered a side effect.
Examples of side effects:
fetching data from an API
updating document title
accessing browser storage
managing timers
subscribing to events
listening for scroll or resize
connecting to WebSockets
updating analytics
React cannot perform these tasks inside the render itself that's why useEffect exists.
useEffect is confusing at first because:
It runs after rendering
The dependency array affects when it runs
It may run again based on changes
It may run with cleanup logic
It affects performance if misused
It replaces multiple lifecycle methods like mount/update/unmount
But once you learn the rules, it becomes predictable.
When your component renders, React does this:
Render the UI
Commit the UI to the screen
Then run your useEffect callback
If dependencies change → React re-runs useEffect.
If component unmounts → React runs cleanup logic.
useEffect is tied to:
rendering
dependency changes
component lifecycle
All in one powerful hook.
The dependency array controls when useEffect runs.
It can be in 3 forms:
Runs after every render.
Runs only once, after the first render (on mount).
Runs whenever those values change.
This behavior allows you to control exactly WHEN logic runs.
Scenario: A user lands on a webpage.
You want to show:
"Welcome back!"
This should only run ONCE.
Where useEffect fits in:
useEffect with an empty dependency array runs only once perfect for:
welcome messages
page loads
analytics initialization
default data loading
first-time executions
This is useEffect simulating a "componentDidMount" behavior.
When a component loads, you want to fetch:
user profile
product list
notifications
dashboard analytics
You don't want to fetch repeatedly. Only ONCE.
useEffect solves this perfectly.
Using useEffect with an empty array ensures the API call runs once after initial load.
React cannot fetch data inside rendering, so useEffect is the ideal place for it.
Example:
You have a cart.
When items increase, you want the page title to show:
"Cart (3 items)"
This logic must run whenever the cart count changes.
useEffect dependency array helps:
Put cartCount in the dependency array now the title updates automatically on every change.
Imagine you want:
a sticky header
a "back to top" button
an animation when the user scrolls
lazy loading images while scrolling
You must listen to the scroll event something React doesn't do automatically.
useEffect is perfect for:
attaching scroll listeners
updating state based on scroll
cleaning up listeners after unmount
Event listeners belong inside effects because they are side effects.
Imagine an app where a user gets auto-logged out after 5 minutes of inactivity.
You would need:
a timer
a cleanup mechanism
state dependency
useEffect is ideal for:
starting timers
clearing timers
resetting timers on change
Timers cannot live inside render only inside useEffect.
Example:
A user selects dark mode.
You want to save the selection in localStorage.
You MUST access the browser API which is a side effect.
useEffect helps sync data whenever preferences change.
If you want responsive behavior like:
shrinking header
rearranging cards
adjusting layout
updating component sizes
You need the window width.
Window properties also must be accessed inside useEffect because they're outside React's rendering process.
Imagine:
A password strength meter
Live form validation
Checking if a username already exists
You want the validation to run every time the input changes.
Dependency-based useEffect is perfect for this.
When the user:
opens a page
clicks something
spends time on a section
scrolls to a point
Analytics tools like Google Analytics or Mixpanel must be triggered.
These are classic side effects.
Thus, useEffect handles these tasks.
Imagine you created:
a timer
an event listener
a WebSocket connection
an animation
a subscription
a long-running background process
When the component is removed, you MUST stop these processes.
useEffect allows cleanup using a returned function.
Cleanup prevents:
memory leaks
duplicate timers
stacked event listeners
unnecessary network calls
slow performance
unexpected behavior
It runs right before the component unmounts or before useEffect runs again.
In class components, developers had to use:
componentDidMount
componentDidUpdate
componentWillUnmount
useEffect replaces all three with one single hook.
It simplifies lifecycle management massively.
Missing dependency values
Causes incorrect or stale data.
Adding wrong dependencies
Leads to infinite loops.
Putting useEffect inside a condition
Breaks the rules of hooks.
Doing heavy logic inside useEffect
Hurts performance.
Not using cleanup
Leads to memory leaks.
Learning these early saves hours of debugging.
React uses the dependency array to:
compare old vs new values
detect changes
trigger re-runs
If the dependency hasn't changed → effect doesn't run.
If it has changed → useEffect executes.
This comparison is fast and optimized by Fiber.
Without useEffect, React apps would not be able to:
fetch data
control browser APIs
respond to user actions
update based on state changes
handle subscriptions
manage animations
sync with external systems
useEffect is the bridge between React's internal world (rendering) and the outside world (everything else).
Once you understand:
side effects
dependency arrays
cleanup functions
how React renders
useEffect stops being confusing and becomes a powerful tool you can rely on daily.
Remember:
useEffect ≠ rendering
useEffect = logic that runs after rendering
useEffect connects React to external processes
useEffect replaces complex lifecycle methods
useEffect powers real-time, dynamic apps
Master useEffect, and React becomes much clearer, easier, and more enjoyable.
Because React wants pure rendering without side effects.
To control when the effect runs once, always, or based on specific values.
No. Only when you start processes like listeners or timers.
Yes. It's recommended to separate effects based on logic.
React Strict Mode intentionally double-invokes effects to detect issues.
No. Only use it for side effects not for every piece of logic.
No. Only for actions outside React's rendering cycle.
Course :