The Provider pattern — or why the WiFi password goes on the wall, not down a relay chain
Your first day at a new office, you need the WiFi password. You ask the person beside you, who asks their lead, who asks the office manager, and it comes back down the same four desks. Four people handled a password none of them needed. Every building solves this the same way in the end — stick it on the wall, and let anyone who needs it just read it. That's the Provider pattern, and knowing where the line sits between "put it on the wall" and "just hand it over" is most of what separates a codebase that's pleasant to change from one where a ten-minute request takes an afternoon.
The issue
First day at a new office. You need the WiFi password. You ask the person beside you — they don't know, they joined last month too. They ask their lead. The lead asks the office manager. The password comes back down the same four desks and by then everyone has been mildly interrupted. Four people handled a string that only one of them actually wanted, and if the lead had been on leave that day the chain would simply have broken.
Every office solves this eventually, and always the same way: the password goes on a card at reception, or gets printed on the wall of the pantry. Same password. But now nobody is a courier. You walk up, you read it, you carry on. The people in between are free to not know it exists.
That is the entire Provider pattern, and the relay-chain version has a name in React too — prop drilling. It's what happens when a value starts at the top of your app and the component that actually wants it is six levels down.
How the chain forms
Nobody sets out to build a relay. It happens one reasonable step at a time. The logged-in user is fetched once, at the top, because that's where you know who's logged in. The little avatar in the corner of the sidebar needs their photo. Between the two sit three components that exist for layout reasons and care about none of this:
function App() {
const [user, setUser] = useState(null);
return <Layout user={user} />;
}
// none of these three want the user. they carry it anyway.
const Layout = ({ user }) => <Sidebar user={user} />;
const Sidebar = ({ user }) => <Nav user={user} />;
const Nav = ({ user }) => <Avatar user={user} />;
const Avatar = ({ user }) => <img src={user.avatarUrl} alt={user.name} />;Written out like that it looks obviously silly, which is unfair — in a real file those five components are in five different folders and nobody ever sees them stacked up like this. What you feel instead is the bill, and it arrives in small instalments.
Fix: put it on the wall
React's answer is Context, and it is genuinely three small pieces: somewhere to put the value, a component that puts it there, and a way to read it from anywhere below.
const UserContext = createContext(null);
// 1. the wall — everything inside it can read the value
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
// 2. the tap — with an error message worth its own line
export function useUser() {
const ctx = useContext(UserContext);
if (!ctx) throw new Error("useUser must be used inside <UserProvider>");
return ctx;
}function App() {
return (
<UserProvider>
<Layout />
</UserProvider>
);
}
const Layout = () => <Sidebar />; // back to knowing nothing
const Sidebar = () => <Nav />;
const Nav = () => <Avatar />;
function Avatar() {
const { user } = useUser(); // reads it straight off the wall
return <img src={user.avatarUrl} alt={user.name} />;
}The small piece worth not skipping is that useUser wrapper and the error inside it. Without it, a component rendered outside the provider quietly gets null and then explodes forty lines later with "cannot read properties of null" — in a file that has nothing to do with the actual mistake. With it, you get a sentence that tells you exactly what you forgot. It's the difference between a labelled shut-off valve and a damp patch on someone else's ceiling.
What belongs on the wall
This is the part that decides whether the pattern helps you or quietly ruins the app, and it's a judgement call rather than a rule. Two questions get you most of the way: does most of the app need this? and does it change rarely? Two yeses and it belongs in a provider.
The list of two-yeses is short and boring, which is the point: the logged-in user, the theme, the language, the currency and locale (the difference between ₹2,499 and $29.99 is needed on every price in the product and changes about once a session), feature flags, and whatever your router puts in the URL. A food-delivery app carries your selected city in exactly this way — the header shows it, search filters by it, the fee calculator uses it, delivery estimates depend on it, and it changes when you move house.
The values that don't belong are the ones that change constantly and are needed in one place: what's currently typed in a search box, a form's field values, scroll position, whether a dropdown is open. Those live where they're used, and pushing them upward into a provider is how a snappy app becomes a laggy one.
The one trap everybody falls into
Here's the thing about the wall: everyone reading it looks up at once. When a context's value changes, every component consuming that context re-renders — not just the ones that care about the part that changed, because there is no "part" as far as React is concerned. So the single tidy-looking AppContext holding the user, the theme, the cart and the search query means every keystroke in the search box re-renders the avatar, the sidebar and the footer.
And there's a sneakier version that catches people who did split their contexts properly. Look at what gets passed as value:
// a brand-new object on every single render of the provider
<UserContext.Provider value={{ user, logout }}>
// the same object until the user actually changes
const value = useMemo(() => ({ user, logout }), [user, logout]);
<UserContext.Provider value={value}>React compares the value by identity, not by what's inside it. Those two curly braces build a fresh object every time the provider renders, so as far as every consumer is concerned the value changed — even though the user is the same person they were a millisecond ago. It's the same identity argument that Issue 011 makes about renders generally, and it's one useMemo away from being a non-issue.
If you don't write the code
This pattern is the reason two teams give wildly different estimates for the same-sounding request, and it's worth understanding because the request usually sounds trivial. "Can we show the user's plan name in the footer?" If there's a provider, that is genuinely fifteen minutes — read it where you need it, ship. If there isn't, someone has to thread that value down through four components that have no business knowing about billing, touch five files, and update the tests of components that don't do anything. Same feature. Same wording in the ticket.
It's also what makes a whole category of things either trivial or impossible. "Log the user out everywhere in the app at once." "Switch the entire product to Hindi." "Show prices in dirhams for UAE users." "Turn the new checkout on for 5% of accounts." Each of these is one value read in forty places — a single afternoon if the plumbing exists, and a refactor disguised as a feature if it doesn't.
The one to listen for from the other direction is a performance complaint that sounds oddly specific: "the whole page flickers when I type in the search box," or "switching the theme takes a second." That shape of bug — an unrelated part of the screen reacting to something small — is very often one context carrying too many tenants, and the fix is a day, not a rewrite.
When not to reach for one
The failure mode here isn't using it wrong, it's using it for everything. Once the provider is easy, every value starts looking global.
The honest test is the one from the office: is this something everyone might need to look up, or is it something one person asked for? Print the first on the wall. Just hand over the second.
For the React-specific detail — the styled-components variant, the HOC shape, and the re-render examples in more depth — patterns.dev's Provider Pattern writeup is the right next stop, and Kent C. Dodds on using Context effectively is where the custom-hook-per-context habit comes from.