Move data around without prop drilling using context, while keeping its re-renders and limits under control.
Open this lesson in KodokonProp drilling refers to those props that pass through three or four intermediate components without being used there, just to reach a leaf of the tree. Every layer they cross becomes coupled to data it does not care about. Context solves this transport problem: createContext creates the channel, a provider publishes a value, and useContext reads it from any descendant. Keep the nuance in mind: context transports state, it does not manage it - the value still lives in a component, here the provider.
import { createContext, useMemo, useState } from "react";
const ThemeContext = createContext(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("dark");
const value = useMemo(
() => ({ theme, setTheme }),
[theme]
);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}When the provider's value changes, all consumers re-render, even deeply nested ones - and React.memo does not protect them, because it compares props, not context. The classic trap makes things worse: writing value={{ theme, setTheme }} creates a fresh object on every render of the provider, hence a new reference, hence cascading re-renders even when theme has not moved. That is what the useMemo above is for. Round out the setup with an access hook that fails loudly outside the provider.
import { useContext } from "react";
function useTheme() {
const context = useContext(ThemeContext);
if (context === null) {
throw new Error(
"useTheme must be inside <ThemeProvider>"
);
}
return context;
}Before creating a context, exhaust the composition option first. If an intermediate layer does nothing but relay a prop, invert the structure: build the element at the top of the tree, where the data lives, and pass it down via children or a dedicated prop. The intermediate layer becomes generic and the data no longer passes through any component that does not use it.
function App({ currentUser }) {
return (
<Layout
header={<TopBar user={currentUser} />}
content={<Dashboard />}
/>
);
}