28.7 Typing Third-Party Hooks

Alright, let’s talk about the real world. You’re not just writing your own beautiful, perfectly typed hooks; you’re also going to be using other people’s. And let’s be honest, some of those people might have been having a very long day when they wrote the type definitions. Our job is to build a sturdy, type-safe scaffold around their code, even when it feels like they’re actively working against us. The golden rule here is simple: Never use any to shut the compiler up. That’s like fixing a gas leak by removing the smoke alarm. We’re better than that. We’re going to understand what we’re dealing with and apply the correct level of type safety.

28.6 Custom Hook Return Types: Tuple vs Object

Alright, let’s settle a debate that’s less about technical superiority and more about developer ergonomics and how you like your data served: in a neatly wrapped object package or a structured tuple takeout box. When you craft a custom hook, the most important contract you design is its return value. It’s the entire API for anyone using your hook. Get it wrong, and you’ll have a chorus of frustrated developers (or future-you) complaining. TypeScript gives you two primary ways to define this return type: an array/tuple or a plain object.

28.5 useCallback and useMemo: Generic Memoization

Right, let’s talk about useCallback and useMemo. You’ve probably heard they’re for “performance,” and then you saw a dozen articles telling you to wrap everything in them. Please, for the love of all that is holy, don’t do that. They’re not magic performance dust; they’re precision tools with very specific—and honestly, somewhat niche—use cases. Misusing them can actually make your app slower and definitely more complicated. I’m here to give you the straight talk on when and why you’d actually need them.

28.4 useContext: Avoiding undefined with Non-Nullable Context

Right, so you’ve decided to use Context. Good for you. It’s a fantastic tool for passing data around without resorting to what I call “prop-drilling” – the tedious and error-prone practice of threading data through a dozen components that don’t care about it just to get it to the one that does. But here’s the first tripwire you’re going to hit, the one that makes everyone’s TypeScript compiler scream in unison: undefined.

28.3 useRef: Mutable Refs vs DOM Refs

Alright, let’s talk about useRef. This is the hook you reach for when you need to break the rules. React is all about the purity of components, the sanctity of props and state leading to predictable renders. useRef is your backdoor, your escape hatch. It says, “I need to remember something, but I swear, pinky promise, its changing has nothing to do with rendering.” It’s the hook of last resort, and when you need it, you really need it.

28.2 useReducer: Typing Actions and State

Right, so you’ve graduated from the tyranny of ten useState calls for a single component and you’re ready to embrace useReducer. It feels more grown-up, doesn’t it? Like switching from a scooter to a manual transmission. But with TypeScript, you’re not just getting the keys—you’re also getting the full, annotated repair manual. Our job is to make sure that manual doesn’t read like it was translated through three languages. The core idea of useReducer is simple: you give it a reducer function (state, action) => newState and an initial state, and it gives you back the current state and a dispatch function to send actions. TypeScript’s job is to lock this whole system down, making it impossible to dispatch an action you didn’t account for or to access a state property that doesn’t exist. It’s your personal bouncer for state management.

28.1 useState<T>: When TypeScript Can and Cannot Infer the Type

Right, let’s talk about useState and TypeScript. This is where you and the compiler start a beautiful, occasionally pedantic, friendship. The good news is that TypeScript is shockingly good at figuring out what type of state you’re trying to manage. The bad news is that when it can’t, it fails in the most spectacularly unhelpful ways. We’re going to learn how to make it work for us, not against us.

19.8 Common Mistakes: Storing contexts in Structs

Right, so you’ve heard the rule: “Don’t store context.Context in a struct.” You’ve probably nodded along, but let’s be honest, you’re also thinking, “But… why? It seems so convenient.” I get it. It feels like the perfect place to stash that cancellation signal so all your methods can use it. It’s a trap. Let’s break down exactly why this is the software equivalent of storing nitroglycerin in a shoebox—it might be fine until it isn’t, and when it goes wrong, it’s spectacular.

19.7 Context in HTTP Handlers and gRPC

Right, let’s talk about where you’ll most likely meet a context.Context: in the belly of an HTTP handler or a gRPC method. This isn’t an academic exercise; it’s the primary control panel your server code has for dealing with the messy reality of the web—clients that vanish, networks that flake, and requests that just take too darn long. The moment an incoming HTTP request knocks on your server’s door, the framework (like net/http or something fancier) creates a context for it and passes it to your handler. This context is your lifeline. It’s pre-wired with two crucial features: a cancellation signal that fires the instant the client disconnects (saving you from talking to a void), and a deadline, which is the server’s polite but firm suggestion for how long you should spend on this whole affair.

19.6 Propagating context Through Call Chains

Right, so you’ve created a context.Context at the top of your call chain—maybe from an HTTP request or a user-driven command. Pat yourself on the back. But that context is utterly useless if it just sits there. Its entire purpose is to be a baton passed through a relay race of function calls, carrying the crucial signals of cancellation and deadlines down the chain. If you drop the baton, your goroutines in the back won’t know the race is over, and they’ll just keep running, pointlessly burning CPU cycles and leaking memory like a sieve. Let’s make sure you’re not that runner.

19.5 WithValue: Passing Request-Scoped Data

Alright, let’s talk about context.WithValue. This is the part of the context package that everyone loves to misuse. It feels like a magical key-value store you can attach to a request. And it is! But it’s a very specific kind of magic, like a spell that only works if you cast it with the exact, correct, and previously agreed-upon incantation. Screw that up, and you’ll summon a eldritch horror of nil pointers and race conditions.

19.4 WithTimeout and WithDeadline: Time-Based Cancellation

Right, let’s talk about time. Specifically, let’s talk about how to tell your code, “Look, if you haven’t figured this out in five seconds, just stop. You’re embarrassing both of us.” This is where context.WithTimeout and context.WithDeadline come in. They’re your primary tools for adding time-based cancellation to your operations, and they’re the reason you don’t have to manually manage a rat’s nest of timers and channels yourself. The difference between them is semantic, but important: WithDeadline is for when you have a specific point in time in mind (“stop at 3:04 PM”), and WithTimeout is for a duration (“stop in 30 seconds”). Under the hood, WithTimeout is literally just a convenience function that calls WithDeadline for you (deadline := time.Now().Add(timeout)), so we’ll often talk about deadlines and they’ll both apply.

19.3 WithCancel: Manual Cancellation

Alright, let’s talk about pulling the plug. Sometimes, you start a task and, for a million different reasons, you need to tell it to stop. Right now. Maybe a user clicked a cancel button, a service you’re calling is taking an eon, or a parent process is shutting down. This is what context.WithCancel is for: it’s your manual override switch. Think of it as creating a cancellation walkie-talkie. You get one channel (context.Context) for listening, and a separate function (context.CancelFunc) for talking—specifically, for shouting “ABORT!” into that channel. The real beauty is that you can hand the listening channel to as many goroutines as you want, and a single shout from the cancel function will reach them all. It’s a one-to-many broadcast system for termination.

19.2 context.Background() and context.TODO()

Right, let’s talk about the two most misunderstood functions in the entire context package: context.Background() and context.TODO(). At first glance, they look identical. They both return an empty, non-cancellable context.Context. If you check the source code (and you should, it’s brilliantly simple), you’ll see they literally do the same thing. So why do two things exist that do the same thing? This isn’t a design flaw; it’s a semantic signpost for you, the programmer.

19.1 Why context Exists: Propagating Cancellation Across Goroutines

Look, let’s be honest. You’ve been there. You fire off a handful of goroutines to fetch some data from a database, ping a microservice, and check a cache. Then you sit back and wait. And wait. And wait. One of those little buggers is stuck, maybe waiting on a network call that will never return, and now your entire request is hung. Your user is frantically hitting refresh, and your service’s memory footprint is slowly ballooning into a Michelin man because every abandoned request leaves its goroutines lying around like dirty socks.

— joke —

...