Search
3 results for “typeerr0r”
-
Our largest deficit is an empathy deficit. This is morally bankrupt, detestable behavior at every level from the killing to the so-called justification of the killing. The killer and everyone involved in excusing child-murder should be in a constant state of pathetic groveling for the public’s forgiveness. These people choose to keep failing the most basic tests of their humanity.
A small group of ambitious men are herding us through a historic decade of lost children and lost dreams across the globe. It’s so wrong that it feels like an impossibly deep fracture
-
Every React codebase I have ever worked in has the same slow disease. It starts
harmless: a component needs some data, so its parent passes it down. That parent
needs it too, so it asks its parent. Three levels later, the component that
actually uses the value is four props away from the component that owns it, and
somebody in the middle is passing props it does not care about.Nobody calls this a problem, because it compiles. It passes review. It ships.
Then a new requirement arrives, the prop needs to be renamed, and you open the
file and find eleven levels of drilling.This note is about the escape hatch React gives you for exactly this, and the
parts of it that are genuinely non-obvious.
The shape of the problem
Consider a cart. The data lives in one place. Three components deep, a button
needs it.function App() {
const cart = useCart(); // owns the statereturn <Layout cart={cart} />;
}function Layout({ cart }) {
// `cart` is passed along purely so a grandchild can use it
return <Sidebar cart={cart} />;
}function Sidebar({ cart }) {
return <CartButton cart={cart} />;
}function CartButton({ cart }) {
return <button>{cart.length} items</button>;
}LayoutandSidebardo not usecart. They are couriers. And this is the
part that should bother you more than the verbosity: couriers are a coupling.
The momentSidebarneeds its own state, it needs its own props, and the
signature ofSidebarnow says something aboutApp's internals. Every
intermediate component has been made aware of a tree it does not own.
What context actually is
Context is a way to put a value on the tree and let any descendant read it
directly, skipping every level in between.import { createContext, useContext } from "react";
const CartContext = createContext(null);
function CartProvider({ children }) {
const [items, setItems] = useState([]);// The value is recomputed on every render, but only the
// consumers below are notified when it changes.
const value = useMemo(
() => ({ items, add: (item) => setItems((s) => [...s, item]) }),
[items],
);return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}And the consumer becomes trivial:
function CartButton() {
const { items } = useContext(CartContext);
return <button>{items.length} items</button>;
}LayoutandSidebarno longer appear in this story at all. That is the entire
point — not fewer characters, fewer components that have to know.
The mistakes that make people avoid the hook
The hook is described as "rarely used", and I think the reason is that most
tutorials teach the API without teaching the failure modes. Here are the four I
see repeatedly.1. Building a new object on every render
This is the big one, and it does not just waste cycles — it makes context feel
slow enough that people conclude context is slow.// Do not do this.
<CartContext.Provider value={{ items, add }}>That object is new on every single render, so every consumer re-renders on every
render of the provider, no matter what. If you have a list of a thousand rows
reading context, you will notice, and you will blame context.Memoize it, as above. Or memoize inside the hook that owns the state:
function useCart() {
const [items, setItems] = useState([]);return useMemo(
() => ({ items, add: (item) => setItems((s) => [...s, item]) }),
[items],
);
}Now the provider passes a stable reference and consumers only wake up when
itemsgenuinely changes.2. Forgetting the default value
createContext(null)means a component used outside the provider readsnull,
and the crash surfaces three files away from the mistake:// Inside CartButton, outside any CartProvider:
const { items } = useContext(CartContext);
items.length; // TypeError: Cannot read properties of nullGive it a real default, and the failure becomes a type error at the point of
misuse:const CartContext = createContext({
items: [],
add: () => {
throw new Error("useCart must be used inside a CartProvider");
},
});The throwing default is the important trick. It turns a silent
null
dereference into a message that names the actual problem.3. Putting everything in one context
Context is not free. Every consumer of a context re-renders when its value
changes — that is the contract. So if you put your entire app state in one
object, changing a toast timer re-renders the navbar.Split by update frequency, not by feature:
<AuthContext.Provider value={auth}> {/* changes rarely */}
<CartContext.Provider value={cart}> {/* changes on click */}
<ToastContext.Provider value={toast}> {/* changes every second */}
{children}Now a ticking toast cannot re-render your cart, because they are separate
contexts with separate subscriptions.4. Reaching for context as an optimisation
Context has a real cost: an extra subscription, a re-render on identity change.
For genuinely hot values — a pointer position, an animation frame, a value that
changes 60 times a second — props are cheaper and more explicit, because you can
pass exactly what is needed to exactly the component that needs it.Use context when the value is genuinely tree-wide. Use props when it is one
branch.
Custom hooks: the part worth copying
WritinguseContext(CartContext)in twenty components means the provider shape
is coupled to twenty call sites. A custom hook hides it behind one name, and
gives you a place to put the awkward bits.import { createContext, useContext } from "react";
const CartContext = createContext(null);
export function useCart() {
const ctx = useContext(CartContext);if (ctx === null) {
throw new Error("useCart must be used within a CartProvider");
}return ctx;
}export function CartProvider({ children, initialItems = [] }) {
// ...provider implementation
}Now the failure mode is a clear stack trace:
Error: useCart must be used within a CartProvider
at useCart (app/context/CartContext.js:14:9)
at CartButton (app/components/CartButton.js:8:20)Compare that to
Cannot read properties of null (reading 'length'). One tells
you what to fix, the other tells you where you crashed.The
initialItemsprop is worth noting too: letting the provider accept its
initial state means tests and stories can mount a provider with known data,
instead of rendering through the entire default flow just to get to the state
they want.
Composition: the part that is genuinely elegant
The strongest argument for context is not prop reduction — it is that it
removes a layer. Compare a provider that wraps and a provider that injects.// Wrapping: the provider knows about your layout.
<AuthProvider>
<AppShell />
</AuthProvider>// Injecting: the provider decides where its consumer goes.
function ThemeProvider({ children }) {
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}function App() {
return (
<ThemeProvider>
<Page />
<ThemeToggle /> {/* injected here, not at the root */}
</ThemeProvider>
);
}With a wrapper you are choosing the structure of the tree, and you have to get
it right at the top. With injection you are choosing where a capability becomes
available, and you colocate the consumer next to the thing that needs it.The
ThemeProviderin this very site is exactly this pattern. It wraps nothing
structural. It only makesuseTheme()work, and the toggle sits wherever the
design wanted it.
When to reach for it
SituationReach forValue used by one branch of the treePropsValue needed across unrelated branchesContextValue changes many times per secondProps or a ref-based storeLibrary-level state (theme, i18n, auth)ContextFrequent, independent updates (cart, toast)Separate contexts per concern
Context is not the answer to state management. It is the answer to one specific
question: how does a value reach a component that is far away without every
component in between having to know it exists?
Closing
This hook isn't so hard to use, is it?Three habits cover almost all of it: memoize the value, give it a default that
fails loudly, and split contexts by update frequency. Write the custom hook so
the provider's shape appears in exactly one file.That is roughly the twelve hours of drilling you do not have to spend.
Thanks a lot for reading!!!#frontend #javascript #react #webdev #software #coding #development #engineering #inclusive #community
Context Into the useContext React Hook