React State Management & Performance
Understanding Context API, Re-renders, React.memo, useMemo, and useCallback
Target Audience: MERN Developers Prerequisites: React Components, Props, State Goal: Understand how React applications scale and how to optimize rendering without overusing performance techniques.
Why Does Managing State Become Difficult as Applications Grow?
When learning React, state seems simple.
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</>
);
}
Only one component owns the state.
Everything is easy.
But real applications aren't made of one component.
Think about Instagram.
App
├── Navbar
├── Sidebar
├── Feed
│ ├── Post
│ ├── Comment
│ └── Like Button
├── Notifications
└── Profile
Now ask yourself:
Who knows if the user is logged in?
Who stores the user's profile?
Who stores the selected theme?
Who knows which notifications are unread?
Suddenly many components need the same information.
Managing state becomes much harder.
Why State Management Becomes Difficult
As applications grow, state begins to spread across many components.
Problems include:
Deep component trees
Shared data
Prop drilling
Duplicate state
Re-rendering
Performance
Maintainability
React itself isn't slow.
Poor state architecture makes applications difficult to maintain.
Example: Authentication
Imagine this component tree.
App
↓
Dashboard
↓
Layout
↓
Navbar
↓
ProfileMenu
↓
Avatar
The logged-in user is stored in App.
const [user, setUser] = useState(...)
Avatar needs the username.
How does it receive it?
Like this.
App
↓
Dashboard(user)
↓
Layout(user)
↓
Navbar(user)
↓
ProfileMenu(user)
↓
Avatar(user)
Every component passes the same prop.
Even though only Avatar actually needs it.
This is called Prop Drilling.
Understanding Prop Drilling
What is Prop Drilling?
Prop drilling is the process of passing props through multiple intermediate components that do not use them.
Example
App
↓
Dashboard
↓
Navbar
↓
Profile
↓
Avatar
Only Avatar needs
user
But every component forwards it.
Example
<App>
↓
<Dashboard user={user}>
↓
<Navbar user={user}>
↓
<Profile user={user}>
↓
<Avatar user={user}/>
Why Does Prop Drilling Happen?
Because React uses one-way data flow.
Data always moves
Parent
↓
Child
↓
Grandchild
There is no shortcut.
Problems with Prop Drilling
1. Repeated Code
Every component forwards props.
<Navbar user={user} />
↓
<Sidebar user={user} />
↓
<Profile user={user} />
Most components don't even use them.
2. Hard Maintenance
Suppose
user
becomes
currentUser
You may need to update dozens of components.
3. Tight Coupling
Intermediate components become dependent on props they don't actually need.
This makes refactoring harder.
4. Poor Readability
Imagine
<App
user={user}
theme={theme}
language={language}
cart={cart}
notifications={notifications}
/>
Now every child forwards everything.
Soon components become cluttered.
Real Example
Authentication
App
↓
Dashboard
↓
Layout
↓
Sidebar
↓
Profile
↓
Avatar
Only Avatar needs
Current User
Yet five components pass it.
React's Solution: Context API
React introduced Context API to solve this problem.
Instead of passing data manually,
Components can access shared data directly.
What is Context?
Context is a mechanism for sharing data across many components without manually passing props.
Think of it as a global storage area for a section of your application.
Without Context
App
↓
Dashboard(user)
↓
Navbar(user)
↓
Profile(user)
↓
Avatar(user)
With Context
App
↓
Provider
↓
Any Component
↓
useContext()
↓
User
No prop drilling.
Provider and Consumer
Context works using two concepts.
Provider
Stores shared data.
<AuthProvider>
<App/>
</AuthProvider>
Consumer
Reads shared data.
Modern React usually uses
const user = useContext(AuthContext);
instead of the older Consumer component.
How Context Works
Provider
↓
Stores Value
↓
Children
↓
useContext()
↓
Receive Value
No matter how deep the component is.
Common Context API Use Cases
Context is excellent for information used by many components.
Examples
Logged-in User
Dark Mode
Language
Currency
User Preferences
Sidebar State
Notification Count
Authentication Example
App
↓
AuthProvider
↓
Dashboard
↓
Navbar
↓
Avatar
Avatar simply does
const user = useContext(AuthContext);
No props required.
Theme Switching
ThemeProvider
↓
Entire App
↓
Button
↓
Card
↓
Navbar
↓
Footer
Every component reads
theme
directly.
When Context API Works Well
Ideal for
Authentication
Theme
Language
User Preferences
Settings
Small and Medium Applications
When Context Isn't Ideal
Avoid putting everything inside Context.
Bad examples
Search input typing
Every form field
Frequently changing local state
Component-specific UI state
Context updates can cause many consumers to re-render.
Use it for shared state—not all state.
Understanding React Re-renders
Many developers think
React updates the whole page.
It doesn't.
React re-renders components.
What is a Render?
A render means React runs your component function again.
function Counter() {
console.log("Render");
return <h1>Hello</h1>;
}
Whenever state changes,
The function runs again.
What Causes a Re-render?
Three common reasons.
1. State Changes
setCount(count + 1);
↓
Component re-renders.
2. Props Change
Parent passes new props.
↓
Child re-renders.
3. Parent Re-renders
If the parent renders,
Children normally render too.
Even if their props didn't change.
Render Flow
State Changes
↓
Parent Renders
↓
Children Render
↓
Grandchildren Render
React does this to ensure the UI stays consistent.
Why Unnecessary Re-renders Happen
Imagine
Dashboard
├── Sidebar
├── Chart
├── Profile
└── Footer
Updating
Profile Name
may cause all children to render.
Even Sidebar didn't change.
Even Footer didn't change.
React is fast,
But unnecessary work is still unnecessary.
Performance Implications
Extra renders mean
More CPU work
More JavaScript execution
Slower UI
Reduced battery life
Poor experience on low-end devices
Most apps don't notice this,
Large dashboards often do.
React.memo
React.memo prevents unnecessary renders.
Without React.memo
Parent Renders
↓
Child Renders
With React.memo
Parent Renders
↓
Props Same?
↓
Yes
↓
Skip Render
Example
const UserCard = React.memo(function UserCard(props) {
return <h1>{props.name}</h1>;
});
If
name
didn't change,
React skips rendering.
When React.memo Helps
Good candidates
Product Cards
Charts
Tables
Expensive Components
Dashboard Widgets
When React.memo Hurts
React.memo itself performs comparisons.
If the component is tiny,
The comparison may cost more than simply rendering.
Don't wrap every component.
useMemo
Some calculations are expensive.
Example
Sorting
Filtering
Searching
Large Calculations
Without memoization,
They run every render.
What useMemo Does
It remembers a computed value.
const sortedProducts = useMemo(() => {
return products.sort(...);
}, [products]);
React recomputes only when
products
changes.
Without useMemo
Render
↓
Sort 10,000 Products
↓
Render Again
↓
Sort Again
↓
Render Again
↓
Sort Again
With useMemo
Render
↓
Sort Once
↓
Reuse Result
↓
Reuse Result
↓
Reuse Result
Common useMemo Use Cases
Large filtering
Sorting
Expensive calculations
Dashboard analytics
Statistics
Graph data
Performance Tradeoff
useMemo also consumes memory.
Don't memoize
2 + 2
username.toUpperCase()
age + 1
Memoize only expensive work.
useCallback
Functions are objects.
Every render creates new function references.
Example
const handleClick = () => {};
Every render
↓
New function
↓
Child receives new prop
↓
Child re-renders.
What useCallback Does
It memoizes functions.
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
Same function reference.
Without useCallback
Render
↓
New Function
↓
Child Re-renders
With useCallback
Render
↓
Same Function
↓
Child Skips Render
useCallback vs useMemo
| useMemo | useCallback |
|---|---|
| Memoizes values | Memoizes functions |
| Returns computed result | Returns function |
| Expensive calculations | Stable callbacks |
Common useCallback Use Cases
Event handlers
Passing callbacks to children
Forms
Lists
React.memo components
Don't Overuse useCallback
Creating callbacks also has overhead.
Don't wrap every function.
Use it when
Passing functions to memoized children
Preventing unnecessary renders
Choosing the Right Optimization
Use Context API
When
Authentication
Theme
Language
Settings
Avoid for frequently changing local state.
Use React.memo
When
Expensive child component
Same props repeatedly
Use useMemo
When
Expensive calculations
Filtering
Sorting
Analytics
Use useCallback
When
Passing functions to memoized children
Stable event handlers
Avoid Premature Optimization
One of the biggest React mistakes.
Don't optimize because you can.
Optimize because you measured a problem.
Remember:
Readable Code
>
Premature Optimization
Profile your application first, then optimise the parts that are actually slow.
Scaling React Applications
As applications grow,
Architecture matters more than optimization.
Think About State Ownership
Ask
Who actually owns this state?
Example
Search Box
↓
Search Component
Don't move it to Context unnecessarily.
Authentication
Entire Application
Context makes sense.
Keep State Close
Store state in the lowest component that needs it.
App
↓
Dashboard
↓
SearchBar
If only SearchBar needs it,
Don't lift it to App.
Organize Components
Avoid
Dashboard.jsx
3000 Lines
Instead
Dashboard
├── Sidebar
├── Navbar
├── Charts
├── Statistics
├── UserTable
└── Footer
State Management Strategy
Local State
↓
Props
↓
Context
↓
Optimization
Don't jump directly to optimization.
Good architecture solves most performance issues.
React Rendering Model
State Changes
↓
React Renders Component
↓
Virtual DOM
↓
Diffing
↓
Real DOM Updates
React only updates what actually changed in the DOM.
Mental Model
Local State
↓
Props
↓
Context
↓
Render
↓
Optimization
Every optimization should support this flow—not complicate it.
Interview Questions
What is Prop Drilling?
Passing props through intermediate components that don't use them.
Why was Context API introduced?
To share data across multiple components without manually passing props through every level.
What causes React to re-render?
State updates
Prop changes
Parent re-renders
What does React.memo do?
It memoizes a component and skips re-rendering if its props have not changed.
What does useMemo do?
Caches the result of an expensive calculation and recomputes it only when dependencies change.
What does useCallback do?
Caches a function reference so the same function can be reused across renders when dependencies haven't changed.
useMemo vs useCallback?
useMemo caches values.
useCallback caches functions.
Should every component use React.memo?
No. It introduces its own comparison cost. Use it only when it provides a measurable benefit.
Is Context API a replacement for local state?
No. Context is designed for shared application state, not every piece of component state.
Key Takeaways
State management becomes challenging as component trees grow and more components need shared data.
Prop drilling is a natural consequence of one-way data flow but can reduce maintainability in deep trees.
Context API eliminates unnecessary prop chains by providing shared state to any descendant component.
Context works best for relatively stable, widely shared data such as authentication, themes, and user preferences.
Every state or prop change can trigger re-renders, making it important to understand React's rendering model.
React.memoavoids unnecessary component renders when props remain unchanged.useMemocaches expensive computed values to avoid repeating costly calculations.useCallbackcaches function references to help prevent unnecessary child renders.Optimisation should be driven by real performance bottlenecks, not added everywhere by default.
Well-designed component architecture and sensible state ownership solve more performance problems than memoization alone.