Was React Context a mistake?
The final thing missing to make it perfect.

Just a guy who loves to write code and watch anime.
Introduction
Was React Context a mistake?
Maybe. ๐
In this post, we'll dive into the problem with React Context, how to effectively use it and alternatives you can use instead.
I assume you're familiar with React Context. Let's still recap it though!
Recap of React Context
React Context is a way to share data between components without having to pass props down through multiple levels.
It's especially nice when you have a lot of components that need to access the same data.
Quick example:
const MyContext = React.createContext(null);
function MyProvider({ children }) {
const [value, setValue] = useState(0);
return <MyContext value={value}>{children}</MyContext>;
}
function MyComponent() {
const value = useContext(MyContext);
return <div>{value}</div>;
}
If you notice I left out .Provider. That's because React 19 is now out and stable. You don't need the .Provider anymore.
The new use hook is beautiful. You can even consume the context conditionally.
Something like this wasn't possible before:
import { use } from "react";
import ThemeContext from "./ThemeContext";
function Heading({ children }) {
if (children == null) {
return null;
}
// This would not work with useContext
// because of the early return
const theme = use(ThemeContext);
return <h1 style={{ color: theme.color }}>{children}</h1>;
}
The Problem with Context
The problem with Context is that any state that changes will trigger a re-render of all components that consume the context.
Even if a component consumes a "set" function, it will still re-render.
Now, if your global state is frequently changing, this can be a problem. Context actually wasn't designed for this. It was designed for global state that doesn't change frequently. Well, to be fair, I don't know if that's 100% true. But that's what it seems like.
That's why I said "maybe" at the beginning of this post. I wouldn't say it's a mistake, but I would say that React has been missing a better way to handle shared state that updates frequently. There is actually an RFC for selectors that's been open forever.
If you use something like Zustand, they've selectors. So you decide what each component should really consume. And whenever that specific value changes, only the components that consume it will re-render.
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
someOtherValue: "hello",
}));
// Will only re-render if count changes
function Counter() {
const count = useStore((state) => state.count);
return <div>{count}</div>;
}
Counter wouldn't re-render if someOtherValue changes.
A problematic example
Let's look at a problematic example with Context to make it clear:
const MyContext = React.createContext(null);
function MyProvider({ children }) {
const [value, setValue] = useState(0);
const [someOtherValue, setSomeOtherValue] = useState("");
const [array, setArray] = useState([]);
return (
<MyContext
value={{
value,
setValue,
someOtherValue,
setSomeOtherValue,
array,
setArray,
}}
>
{children}
</MyContext>
);
}
function ComponentOne() {
const { value, setValue } = useContext(MyContext);
return <div>{value}</div>;
}
function ComponentTwo() {
const { someOtherValue, setSomeOtherValue } = useContext(MyContext);
return <div>{someOtherValue}</div>;
}
function ComponentThree() {
const { array, setArray } = useContext(MyContext);
return <div>{array.length}</div>;
}
function ComponentFour() {
const { value, setValue } = useContext(MyContext);
return <div>{value}</div>;
}
If you change value, every single component that consumes the context will re-render. The problem? Unnecessary re-renders happen. There is no reason for ComponentTwo to re-render when value changes!
Work effectively with Context
1. Smaller Contexts
Instead of having a single large context, split it into smaller contexts.
This way, you can control which components re-render when a specific state changes.
Example:
// โ Bad: One big context
const UserContext = createContext({
profile: {},
settings: {},
notifications: {},
friends: [],
});
// โ
Better: Split contexts
const UserProfileContext = createContext({});
const UserSettingsContext = createContext({});
const NotificationsContext = createContext({});
const FriendsContext = createContext({});
Personally, if I find myself in this situation, I'll reach for Jotai or Zustand. Not always, it depends.
2. Use Jotai or Zustand (or something else)
You don't have to use Jotai or Zustand specifically. But the point here is to use a state management library that's better at dealing with frequent updates while keeping re-renders to a minimum.
The Recursive Context Pattern
I don't know if it's called "recursive context pattern", but I did wanna share one thing I find cool with context. Is that you can nest the same context inside of itself.
// Create context to track the nearest accordion's state
const AccordionContext = createContext(null);
// Hook to find nearest accordion controller
const useNearestAccordion = () => {
const context = useContext(AccordionContext);
if (!context) throw new Error("Must be within Accordion");
return context;
};
// Main accordion that provides context
const Accordion = ({ children }) => {
const [openSections, setOpenSections] = useState(new Set());
return (
<AccordionContext value={{ openSections, setOpenSections }}>
<div className="accordion">{children}</div>
</AccordionContext>
);
};
// Section automatically connects to nearest parent accordion
const Section = ({ id, children }) => {
const { openSections, setOpenSections } = useNearestAccordion();
return (
<div>
{/* Each section works with its nearest accordion context */}
<button
onClick={() => {
setOpenSections((current) => {
const next = new Set(current);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
}}
>
Toggle Section {id}
</button>
{openSections.has(id) && children}
</div>
);
};
// Usage example showing nesting
const Example = () => (
<Accordion>
<Section id="1"> {/* Uses first Accordion */}
Content 1
<Accordion>
<Section id="1.1">Nested content</Section> {/* Uses second Accordion */}
</Accordion>
</Section>
</Accordion>
);
In this example, Section can find the nearest Accordion context and use it. You can of course have components in there that consume the context too.
This is a pattern you'll find in a lot of UI libraries.
Conclusion
Context is a powerful tool, but it's not always the best choice.
Was it a mistake? I don't think so. The only thing missing is selectors. That'll be a game changer. I guess for now the most reliable approach is just to have smaller contexts. Although, kind of meh (which is why I reach for something else in such cases ๐).
If you find yourself in a situation where you're frequently updating state and re-renders are causing performance issues, consider using a state management library like Jotai or Zustand.






