Skip to main content

Command Palette

Search for a command to run...

React State Management & Performance

Updated
11 min readView as Markdown

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.memo avoids unnecessary component renders when props remain unchanged.

  • useMemo caches expensive computed values to avoid repeating costly calculations.

  • useCallback caches 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.