# State Management: Context API, Prop Drilling, React.memo, useMemo, and useCallback

The first time I started building slightly bigger React applications, I didn't really have a state management problem.

At least…

I didn't think I did.

I had a component.

The component had some state.

I passed that state to another component.

Then another component needed it.

So I passed it again.

And then another one needed the same data.

So I passed it again.

At that point I started thinking:

**Why am I passing this through three components when only the last component actually needs it?**

That was my first real encounter with **prop drilling**.

And honestly…

that was only the beginning.

Once applications grow, another set of questions starts appearing.

Where should state live?

Should I pass it through props?

Should I use Context?

Why is this component rendering again?

Do I need `React.memo`?

What exactly is `useMemo` remembering?

And why would I ever need `useCallback`?

At first, all of these felt like separate React features.

But eventually I started seeing them as different tools for solving different problems.

And the important part wasn't learning:

```text
Context
memo
useMemo
useCallback
```

as four things to memorize.

It was understanding **when the problem actually requires each one**.

* * *

# Why Does State Management Become Difficult?

![](https://cdn.hashnode.com/uploads/covers/69514a87560e53902880bfd9/cec5112e-b453-46b0-892c-1afdb069a40a.png align="center")

When an application is small, state is usually easy.

Imagine:

```text
App
 └── Counter
```

`Counter` has its own state.

No problem.

But real applications don't stay like that.

A dashboard might look like:

```text
App
 ├── Navbar
 ├── Sidebar
 └── Dashboard
      ├── UserProfile
      ├── Stats
      ├── Chart
      └── Activity
```

Now imagine the current user information is needed by:

```text
Navbar
UserProfile
Activity
Sidebar
```

Where should that information live?

Maybe it lives near the top.

Then we pass it down.

And that's when things start getting interesting.

* * *

# Prop Drilling

![](https://cdn.hashnode.com/uploads/covers/69514a87560e53902880bfd9/63d57912-56f0-47e9-9866-5bba31a7bd23.png align="center")

Suppose `App` has the user:

```jsx
function App() {
  const user = {
    name: "Sahil",
  };

  return <Dashboard user={user} />;
}
```

Then:

```jsx
function Dashboard({ user }) {
  return <Profile user={user} />;
}
```

And:

```jsx
function Profile({ user }) {
  return <h2>{user.name}</h2>;
}
```

The `Dashboard` doesn't actually need the user.

It is just passing it along.

So the data flow looks like:

```text
App
 ↓ user
Dashboard
 ↓ user
Profile
```

That is **prop drilling**.

And the problem isn't that passing props is bad.

Props are one of the normal ways React components communicate.

The problem starts when the data has to travel through components that don't actually care about it.

* * *

# This Wasn't A Problem At First

I think this is important.

When I first heard people complain about prop drilling, I thought:

Why not just use Context everywhere?

But that's not necessarily better.

If I have:

```text
Parent
 ↓
Child
 ↓
Grandchild
```

and the child needs to pass one value to the grandchild…

passing a prop is completely fine.

The problem appears when the tree becomes something like:

```text
App
 ↓
Layout
 ↓
Dashboard
 ↓
Section
 ↓
Panel
 ↓
UserProfile
```

And now every component in between is carrying:

```text
user
```

even though most of them don't use it.

That makes the code harder to understand.

* * *

# The Component Tree Starts Getting Noisy

Imagine:

```text
App
 │
 ↓
Layout
 │
 ↓
Dashboard
 │
 ↓
Sidebar
 │
 ↓
Profile
```

And the user object travels through every level.

The actual flow becomes:

```text
App
  │
  │ user
  ↓
Layout
  │
  │ user
  ↓
Dashboard
  │
  │ user
  ↓
Sidebar
  │
  │ user
  ↓
Profile
```

Looking at this…

I started thinking:

**There has to be another way.**

And that's where Context API came in.

* * *

# Context API

The basic idea behind Context is pretty simple.

Instead of passing some data manually through every level…

a parent can make that data available to components deeper in the tree.

Think about it like:

```text
Provider
   │
   │ shared value
   ↓
Component tree
   │
   ├── Child
   ├── Child
   └── Child
         │
         ↓
      Consumer
```

The components that need the data can access it without every intermediate component having to receive and pass it.

That was the part that made Context click for me.

* * *

# Creating A Context

![](https://cdn.hashnode.com/uploads/covers/69514a87560e53902880bfd9/4f5a2033-1aa9-4c59-8e99-b3b2e8ff05fa.png align="center")

A very simple example:

```jsx
import { createContext } from "react";

const UserContext = createContext(null);
```

Then we can provide a value:

```jsx
<UserContext.Provider value={user}>
  <Dashboard />
</UserContext.Provider>
```

Now components inside that provider can access the value.

For example:

```jsx
import { useContext } from "react";

function Profile() {
  const user = useContext(UserContext);

  return <h2>{user.name}</h2>;
}
```

Now we don't need:

```text
App
 ↓ user
Dashboard
 ↓ user
Sidebar
 ↓ user
Profile
```

Instead:

```text
UserContext
     │
     ↓
   Profile
```

That's a much cleaner relationship when the data genuinely needs to be shared.

* * *

# Context Doesn't Mean "Global Everything"

This was another thing I had to be careful about.

Context is useful.

But that doesn't mean every piece of state should go into Context.

For example, if I have:

```jsx
const [isOpen, setIsOpen] = useState(false);
```

for one dropdown…

I probably don't need:

```text
Global Dropdown Context
```

That would be overkill.

I started thinking of Context as useful for values that are genuinely shared across a part of the application.

Things like:

```text
Authentication
Theme
User preferences
Application settings
```

These are much better examples.

* * *

# Authentication Is A Good Example

Imagine the application needs to know the current user.

A lot of components might care about that.

```text
Navbar
Profile
Dashboard
Settings
Sidebar
```

Instead of passing:

```text
user
```

through every component, we can have something like:

```text
AuthProvider
     │
     ├── Navbar
     ├── Dashboard
     ├── Profile
     └── Settings
```

And those components can access the authentication information they need.

That feels much more natural.

* * *

# Theme Is Another Simple Example

Imagine a theme switcher.

The application can have:

```text
light
dark
```

And several components need to know the current theme.

Without Context:

```text
App
 ↓ theme
Layout
 ↓ theme
Dashboard
 ↓ theme
Card
```

With Context:

```text
ThemeProvider
      │
      ├── Navbar
      ├── Dashboard
      └── Card
```

The components that care about the theme can access it.

Again…

much less prop passing.

* * *

# But Then Something Else Happened

I had solved one problem.

Prop drilling.

Then I noticed another problem.

**Re-renders.**

I started seeing components render again even when I didn't think they needed to.

And that made me ask:

**What exactly causes a component to render again?**

That question is important before talking about `React.memo`.

* * *

# Understanding Re-renders

![](https://cdn.hashnode.com/uploads/covers/69514a87560e53902880bfd9/c83ef775-ea30-4293-bf49-1cdd88e996b6.png align="center")

Suppose I have:

```jsx
function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        {count}
      </button>

      <Profile />
    </div>
  );
}
```

When `count` changes, `App` renders again.

That makes sense.

But what about:

```jsx
<Profile />
```

Does `Profile` render again too?

This is where I started paying more attention to the relationship between parent and child components.

A parent rendering can cause its children to be rendered again as part of React's normal rendering process.

That doesn't necessarily mean the DOM is changed.

And it doesn't automatically mean there's a performance problem.

That distinction is important.

* * *

# Re-render Doesn't Mean "The Screen Changed"

This confused me for a while.

I used to think:

```text
Component renders
=
DOM changes
```

Not necessarily.

A component can render again and React can determine that the actual DOM doesn't need a meaningful update.

So:

```text
Render
```

and:

```text
DOM update
```

are not the same thing.

This is important because otherwise you start trying to prevent every render.

And that's not the goal.

* * *

# React.memo

![](https://cdn.hashnode.com/uploads/covers/69514a87560e53902880bfd9/b0e1bb96-7236-4017-983b-092a8a8145b3.png align="center")

Now `React.memo` starts making more sense.

Suppose I have:

```jsx
const Profile = React.memo(function Profile() {
  return <h2>Profile</h2>;
});
```

The idea is that React can skip rendering this component when its props haven't changed.

So imagine:

```text
App
 ├── Counter
 └── Profile
```

`Counter` changes.

`App` renders again.

But `Profile` receives the same props.

With `React.memo`, React can avoid rendering `Profile` again in that situation.

The mental model I use is:

```text
Parent renders
      ↓
Child receives same props?
      ↓
Yes
      ↓
React.memo may skip child render
```

That's the basic idea.

* * *

# React.memo Isn't A Magic "Don't Render" Button

This is where I think it's easy to go wrong.

You shouldn't look at:

```jsx
React.memo(Profile)
```

and think:

**"Now Profile will never re-render."**

No.

There are other reasons a component can render.

For example, if the component has its own state and that state changes, it can render.

Also, if its props change, React may need to render it.

So `React.memo` is really about **avoiding certain unnecessary renders based on props**.

* * *

# When React.memo Can Actually Help

Imagine a component that:

*   renders frequently
    
*   is relatively expensive
    
*   receives stable props
    
*   is part of a frequently updating parent
    

Then memoization can potentially help.

For example:

```text
Dashboard
 ├── LiveCounter
 ├── ExpensiveChart
 └── UserProfile
```

If `LiveCounter` updates frequently but `UserProfile` doesn't change…

you may not want the profile component doing unnecessary rendering work every time.

That's a situation where `React.memo` might make sense.

But again…

measure first.

* * *

# When React.memo Can Hurt

This was something I didn't think about initially.

Memoization isn't free.

React has to compare props.

And now the code also has another optimization concept to understand.

If the component is tiny and cheap to render…

adding memoization may not provide meaningful benefit.

You can end up with code that's more complicated without solving an actual problem.

That's why I started thinking:

**Optimization should solve a problem.**

Not just make the code look advanced.

* * *

# useMemo

Then came:

```jsx
useMemo
```

At first I confused it with `React.memo`.

The names don't exactly help.

I started remembering the difference like this:

```text
React.memo
↓
Memoize a component

useMemo
↓
Memoize a calculated value
```

For example:

```jsx
const total = useMemo(() => {
  return calculateTotal(products);
}, [products]);
```

The idea is that React can reuse the calculated result until the dependencies change.

* * *

# Why Would I Need useMemo?

![](https://cdn.hashnode.com/uploads/covers/69514a87560e53902880bfd9/76f961d4-9ad1-4e50-98c8-77d84b2c1f49.png align="center")

Imagine a calculation that is actually expensive.

Something like:

```jsx
const result = calculateSomethingVeryExpensive(data);
```

If the component renders frequently, that calculation might happen repeatedly.

If the inputs haven't changed, maybe I don't need to calculate it again.

That's where `useMemo` can help.

Think:

```text
Data
 ↓
Expensive calculation
 ↓
Result
```

With memoization:

```text
Data unchanged
 ↓
Reuse previous result
```

That's the basic idea.

* * *

# useMemo Isn't For Every Calculation

This is important.

Don't do this everywhere:

```jsx
const name = useMemo(() => {
  return "Sahil";
}, []);
```

There is no useful reason for that.

That's more complicated than:

```jsx
const name = "Sahil";
```

`useMemo` is about avoiding unnecessary expensive calculations or maintaining a stable value when that actually matters.

It's an optimization tool.

Not a replacement for normal variables.

* * *

# useCallback

Then there was:

```jsx
useCallback
```

And at first I thought:

Okay…

isn't this basically `useMemo`?

Almost.

The easiest mental model I use is:

```text
useMemo
↓
Memoizes a value

useCallback
↓
Memoizes a function
```

For example:

```jsx
const handleClick = useCallback(() => {
  console.log("Clicked");
}, []);
```

Now the function reference can remain stable between renders as long as the dependencies don't change.

And that can matter when passing functions to memoized child components.

* * *

# Why Would A Function Need Memoization?

This took me a while to understand.

Suppose:

```jsx
function Parent() {
  const handleClick = () => {
    console.log("Clicked");
  };

  return <Child onClick={handleClick} />;
}
```

Every time `Parent` renders, a new function is created.

Conceptually:

```text
Render 1
handleClick → function A

Render 2
handleClick → function B
```

Even though the function does exactly the same thing, the references are different.

If `Child` is memoized:

```jsx
const Child = React.memo(...);
```

the changing function reference can make the child appear to have changed props.

That's one situation where `useCallback` can become useful.

* * *

# useCallback And React.memo Often Work Together

![](https://cdn.hashnode.com/uploads/covers/69514a87560e53902880bfd9/e11cf653-df9a-4d98-9fa2-226d4f47d9af.png align="center")

This is probably the most useful relationship to understand.

Imagine:

```text
Parent
  │
  │ onClick
  ↓
Memoized Child
```

If the parent creates a new function every render:

```text
Parent renders
     ↓
New function reference
     ↓
Child receives different prop
     ↓
Child may render again
```

With `useCallback`:

```text
Parent renders
     ↓
Same function reference
     ↓
Child receives same prop
     ↓
React.memo can potentially skip child render
```

Now I understood why people use these two together.

Not because:

**"useCallback makes functions faster."**

That's not the point.

It's about **referential stability** when that stability matters for rendering behavior.

* * *

# useMemo vs useCallback

The simplest comparison I keep in my head is:

| Tool | What it memoizes |
| --- | --- |
| `React.memo` | Component rendering based on props |
| `useMemo` | Calculated value |
| `useCallback` | Function reference |

So:

```text
React.memo
→ Component

useMemo
→ Value

useCallback
→ Function
```

That's enough for the foundation.

* * *

# Context Can Also Affect Rendering

This is where things become interesting again.

Context solves prop drilling.

But Context isn't automatically a performance solution.

If a context value changes, components consuming that context can need to render again.

For example:

```text
ThemeContext
     │
     ├── Navbar
     ├── Dashboard
     └── Profile
```

If the context value changes…

the consumers that depend on it can be affected.

So Context solves one problem:

**How do I share this value without passing it through every component?**

It doesn't automatically solve:

**How do I prevent unnecessary rendering?**

Those are different problems.

That distinction was important for me.

* * *

# Context API vs Prop Drilling

![](https://cdn.hashnode.com/uploads/covers/69514a87560e53902880bfd9/4b916478-3c1b-4f32-b825-75fe49cec460.png align="center")

Now the difference is much clearer.

### Prop Drilling

```text
App
 ↓ props
Layout
 ↓ props
Dashboard
 ↓ props
Profile
```

### Context

```text
        Provider
           │
     ┌─────┼─────┐
     ↓     ↓     ↓
   Layout Dashboard Profile
                       ↑
                    consumes
```

Prop drilling is explicit data passing.

Context is shared access within a provider's tree.

Neither is automatically better.

It depends on the problem.

* * *

# When I Would Use Context

I think Context makes sense when the same information is needed across many components.

For example:

```text
Authentication
Theme
Language
User preferences
Application configuration
```

These are good candidates.

If only one child needs a value…

I'd probably just use props.

If I find myself passing the same value through many unrelated layers…

I'd start considering Context.

* * *

# When I Wouldn't Use Context

I wouldn't create a context for:

```text
One button
One dropdown
One small form
One local UI state
```

For example:

```jsx
const [isOpen, setIsOpen] = useState(false);
```

That's perfectly fine inside the component that owns the dropdown.

No need to make it global.

* * *

# Choosing The Right Tool

![](https://cdn.hashnode.com/uploads/covers/69514a87560e53902880bfd9/eeafb9eb-0452-4163-a3a2-63f163376f0c.png align="center")

This is probably the most important section for me.

Because once you learn these tools, it's tempting to use all of them.

I don't think that's the right approach.

I started thinking about them as questions.

### Do I need to share state?

Maybe:

```text
Context
```

### Am I passing props through many components?

Maybe:

```text
Context
```

### Is a child rendering unnecessarily because its props haven't changed?

Maybe:

```text
React.memo
```

### Is an expensive calculation happening repeatedly?

Maybe:

```text
useMemo
```

### Is a function reference causing unnecessary child renders?

Maybe:

```text
useCallback
```

That is much better than:

**"I learned five optimization tools, so I'm going to use all five."**

* * *

# Don't Optimize Everything

This was probably one of the most important lessons.

When I first learned about:

```text
React.memo
useMemo
useCallback
```

they looked like things I should use everywhere.

But that would make the application harder to read.

And not every render is bad.

Not every calculation is expensive.

Not every function reference causes a problem.

Sometimes the simplest code is already fast enough.

So before optimizing, I would ask:

```text
Is there actually a performance problem?
```

Then:

```text
What is causing it?
```

And only then:

```text
What is the smallest useful optimization?
```

That mindset is much healthier.

* * *

# State Ownership Matters More Than I Initially Thought

One thing I started noticing as applications got bigger was that many state problems are actually architecture problems.

For example:

```text
Where should this state live?
```

is often more important than:

```text
Which optimization hook should I use?
```

If state is placed in the wrong component, you might end up passing it everywhere.

Or you might cause a large part of the tree to render when only one small component needed the update.

So I started thinking:

**Keep state as close as possible to where it is actually needed.**

Then move it higher only when multiple components genuinely need it.

That simple idea can prevent a lot of unnecessary complexity.

* * *

# A Simple Dashboard Example

![](https://cdn.hashnode.com/uploads/covers/69514a87560e53902880bfd9/4477237d-b0ae-4956-b110-288202131084.png align="center")

Imagine:

```text
App
 ├── Navbar
 ├── Sidebar
 └── Dashboard
      ├── Stats
      ├── Chart
      └── Activity
```

Authentication information might belong in Context:

```text
AuthProvider
      │
      ↓
      App
```

The dashboard's selected date might belong in:

```text
Dashboard
```

The chart's internal UI state might belong in:

```text
Chart
```

An expensive chart calculation might use:

```text
useMemo
```

A memoized chart component might use:

```text
React.memo
```

And a callback passed into that chart might use:

```text
useCallback
```

Now each tool has a reason.

That's the part I wanted to understand.

* * *

# A Mental Model I Use Now

Instead of remembering all the APIs separately, I think about the application like this:

```text
                 Application
                      │
                Component Tree
                      │
          ┌───────────┼───────────┐
          ↓           ↓           ↓
        Local       Shared      Derived
        State        State        Data
          │           │            │
          ↓           ↓            ↓
       useState     Context      useMemo
                                  │
                                  ↓
                              Expensive
                              calculation
```

And for rendering:

```text
Parent
  ↓
Child
  ↓
Props change?
  ↓
React.memo may help
```

And for function references:

```text
Parent
  ↓
useCallback
  ↓
Stable function reference
  ↓
Memoized Child
```

Now the tools aren't random anymore.

They're solving different problems.

* * *

# What Finally Made State Management Click

I used to think state management was mostly about choosing the right React API.

Now I think it's more about **architecture**.

Where should state live?

Who needs it?

Who owns it?

How far does it need to travel?

Does it really need to be shared?

Is something actually slow?

Is the component rendering unnecessarily?

Only after answering those questions should I start thinking about:

```text
Context
React.memo
useMemo
useCallback
```

That changed the way I approach React applications.

Instead of starting with:

**"Which optimization should I use?"**

I start with:

**"What problem am I actually trying to solve?"**

* * *

# Quick Recap

*   Prop drilling happens when data is passed through components that don't actually need it.
    
*   Props are still a normal and useful way to pass data.
    
*   Context can help when data needs to be shared across many components.
    
*   Context is useful for things like authentication, themes, and shared settings.
    
*   Context isn't a replacement for all local state.
    
*   A parent re-render doesn't automatically mean the DOM changes everywhere.
    
*   `React.memo` can skip some child renders when props haven't changed.
    
*   `useMemo` memoizes a calculated value.
    
*   `useCallback` memoizes a function reference.
    
*   `React.memo`, `useMemo`, and `useCallback` are optimization tools, not requirements.
    
*   Memoization has its own costs and can make code more complicated.
    
*   State ownership and component architecture can have a bigger performance impact than adding memoization everywhere.
    
*   The best optimization is usually the one that solves an actual problem.
    

That's the foundation.

* * *

# Conclusion

I started this topic thinking state management was mostly about:

**"Where do I store my state?"**

But I think the bigger question is:

**"Who actually needs this state?"**

If only one component needs it…

keep it there.

If a parent and child need it…

props may be enough.

If many components need the same information…

Context might make sense.

And if the application starts rendering more than it needs to…

then I can start investigating why.

Maybe the component structure needs work.

Maybe `React.memo` helps.

Maybe an expensive calculation needs `useMemo`.

Maybe a function reference needs `useCallback`.

But the important thing is not to reach for these tools automatically.

I don't want to write:

```text
Context
+
React.memo
+
useMemo
+
useCallback
```

just because I can.

I want to know **why** I'm using each one.

That was probably the biggest thing I learned from this topic.

State management isn't just about managing state.

It's about deciding:

**where the state belongs, who needs it, how data should move, and when optimization is actually necessary.**

And honestly…

I think that's what makes a React application easier to scale.

Not having the most hooks.

Not having the most memoization.

Not having the most abstractions.

Just having a clear reason for where things live and why they exist.

If you like this simple learning-style explanation, I write more notes at **devwithsahil.hashnode.dev** and share my progress on LinkedIn 🙂

#react #reactjs #javascript #frontend #webdevelopment
