React Context : A beautiful Nightmare
TL;DR — React context was introduced to tackle another nightmare — prop drilling.
React Context : A beautiful Nightmare

React Context
TL;DR — React context was introduced to tackle another nightmare — prop drilling.
Prop drilling is a state where you pass data from parent component to multiple layers of children component.
For small scale its not an issue but mostly everybody hates it. (I also)
This is where React Context comes to solve problem in three steps:
- Create a context.
- Create a provider
- Wrap your component with provider
- Consuming the context
Ok not three but four.
1. Creating a Context
this is the first step of this whole beautiful mess. React provides an API which is createContext().
Let me setup an analogy.
Think of context as setup a white board where something will be written and read by everyone. Currently white board will be empty.
That’s it.
Now, Lets say I want to store user information after logging here so that I can access info in child components.
Lets start with the first step which is creating a context and this part will be remain same for every situations.
const UserContext = createContext(null);
We need to pass a default value to createContext as a fall back and if you don’t have any then pass null.
Because when you consume the context and try to access the value you will get null if nothing is there.
But you should not be this lazy, use typescript to define the data this context will hold.
// context type
export type UserContextType = {
user: string | null;
setUser: (user: string | null) => void;
};
// creation of context
export const UserContext = createContext<UserContextType | null>(null);
In above code I have created a type for my UserContext .
By this type I am damn sure what my context will hold and also it will help with autocomplete while working with provider.
I have used union to be safe here also, if nothing is passed its null. (its all typescript terminology)
Now coming the hook its a safety net, if you try to access the context outside of provider than it will kick it.
And apart from this it provides the autocomplete in the components and we won’t need to check for the null in every component.
2. Creating a Provider
We have done the setup with createContext API. Its time to create a provider which is again simple.
There are two version of it before React 19 and after it a simple change in syntax is there.
Continuing with our analogy.
This is where we can and others can write something on the board and modify it (with respect to the types we have defined).
import { useState } from "react";
import { UserContext } from "./UserContext";
type Props = {
children: React.ReactNode,
};
export function UserProvider({ children }: Props) {
const [user, setUser] = useState<string | null>("mike");
return (
// here is the provider we are talking about
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
This is the last piece which used to seem cryptic to me.
So, here we have defined the properties / states and pass to value prop of provider that we need to consume across the components.
Remember we defined the types for context this will be useful here when you try to pass some other things you will get typescript compile time error.

As you can clearly see the benefits of using typescript here (remember my tweet).
Ok so what’s changed in React 19. Well you don’t need to write UserContext.Provider.
I really like the react team efforts to simplifying the APIs.
return (
<UserContext value={{ user, setUser }}>
{children}
</UserContext>
);
It works same less annoyance with syntax now. but remember its works with react 19 and which working on old projects you will need to know the old way lol. (fuck you old projects)
Ok enough talk we have only reached second step.
3. Wrap your component with provider
So our provider is ready to receive react component as children and whatever component you put inside this will be provided everything from this context.
for now I have provided to whole App from main. you don’t need to do this btw.
Provide the context to the component that requires it — to keep things simple.
Cause if you provide the context to whole children then any children who have subscribed to the context (used the values) will rerender unececssarily.
In simple words,
- When context value changes
- All the consumers of that context will rerender.
createRoot(document.getElementById("root")!).render(
<UserProvider>
<App />
</UserProvider>
);
Now inside <App /> component all its children will have access to the UserContext. That’s it.
Lets move to last step where we will consume the context (finally… after this much majdoori).
4. Consuming the context
Now there are two ways to consume the context.
- Directly using useContext API
const value = useContext(UserContext)
Now you can access by value.user or value.setUser.
But you can also destructure things
const {user, setUser} = useContext(UserContext)
But there is an issue here typeScript will complain.

As you can see typescript is saying that setUser doesn’t exist but I have defined it.
Its because of the union type we have used that tells typescript that either it can be a value or null.
So now typescript won’t give autocomplete if we don’t check for null before.
There are two easy fixes here to get the fucking autocomplete properly.
- Use non-null assertion
// non-null assertion:
const { user, setUser } = useContext(UserContext)!;
By adding ! we are telling to typescript that trust me bro. I know the value is not null or undefined. (Bhai ne bola to man lene ka).
But I know you know we are fucking stupid we code so this is not a good approach.
Making typescript shut up at compile time to make code look error is not a good move.
Runtime error will kick you ass when time comes.
With that said here is the safer and recommended approach.
2. Create a hook
This is the recommended and scalable approach because we are explicitly checking for null/undefined values and throwing error and returning the values after that.
export function useUser() {
const ctx = useContext(UserContext);
if (!ctx) throw new Error("useUser must be used within a UserProvider");
return ctx;
}
Now typescript will not throw error at compile time. Giving you proper suggestion and you don’t need make any random use of things to shut typescript.
This make me respect typescript more.
TypeScript is like my girlfriend. she warns me early because fixing things later is expensive.

Now you can easily call this hook and everything works like breeze.
With that said this whole context setup things is now done with all the steps.
I hope this article might have made some sense to you.
Thanks for reading.
Next article might be on state managment hell.
메타데이터
- post_id
- 55e00efcabca
- slug
- react-context-a-beautiful-nightmare-55e00efcabca
- url
- https://medium.com/@sankitdev/react-context-a-beautiful-nightmare-55e00efcabca
- canonical_url
- https://medium.com/@sankitdev/react-context-a-beautiful-nightmare-55e00efcabca
- author_url
- https://medium.com/@sankitdev
- status
- ok
- fetched_at
- 2026-07-30 00:42:56