Advanced Hooks 8 exercises

Overloading Functions in TypeScript

Let's declare some function overloads for maybeReturnsString that will have different behaviors based on the input parameters.

We'll do this by adding another function above maybeReturnsString with the same name. This function will take in defaultString and return a string.


Loading solution


00:00 So, what we want to do here is we want to declare some overloads that declare different behaviors for this MaybeReturnString function. Now, what we want to do here is we actually, the way you can make this work is you can actually add another function above this with the same name, MaybeReturnString.

00:18 And in here, we're going to encode what happens when you pass it a default string. So this default string is going to be a string, then we know that it returns a string inside here. So now MaybeReturnsString, you pass it a default string, and it has to return a string.

00:35 So now we can see that example one is working, MaybeReturnsHello, beautiful, but we can also see that the second one here is actually just not working at all. It says expected one arguments, but got zero. This doesn't seem to make sense because we've got this signature here, which is basically

00:53 saying, okay, you can either pass this in or not, but here's the thing. In overloads, you have these signatures above it, and you can actually have many of these if you want to. Currently, this one has five that are all the same, but this one here is the implementation signature, and the implementation signature can't be seen from outside the function.

01:12 So anything you define in here, it has to be compatible, so it can't request a number, for instance. So this one, this overload signature is not compatible with its implementation signature, but this one's not visible to outside. So in order to declare kind of two different types of behavior, which is what we have here,

01:31 we actually want to declare two different overloads, or rather three different overloads, but these two are the kind of external facing ones, and this one is the implementation one. So this one, MaybeReturnsString, here, we actually want it to pass no arguments at all,

01:48 and it's going to return string or undefined. MaybeReturnString, if you don't pass anything, returns string or undefined. So it hits this branch here. This is really, really nice, and also, by the way, we can actually add different js.comments per overload.

02:03 So we can say, hello world, up here, and let's say, hello with default string. And then if we hover over each one here, it says, hello with default string, and hello world. Really cool. So you might notice, though, that this sort of default string inside here, it's actually

02:23 not very safe, really, because technically, we could return a bunch of different stuff from here. We could return string or undefined or number or boolean, and actually, these, it seems, can be wider than this one here, but it's not really doing any kind of checking from

02:39 the function overloads to actually the function overload implementation, or it's doing kind of a tiny little bit. So for instance, if we say, default string, or you could pass a number, or you could pass a boolean, or things like this. This is still safe, because externally, we're still only passing a string, so it's never

02:58 going to hit this number or boolean branch, but you can see there's just a little bit of a disconnect between the function implementation signature and the overloads that appear above it. So this really maps on well onto our useState logic, and this is really important for when you're wrapping useState.

03:15 So if we go back to the problem here, then we can command click, and we can see that useState is a generic function that takes in this S here, which represents the state, and we've got one overload where the initial state is declared here, and it's basically

03:31 saying, okay, this can't be undefined, whereas this one down here, useState, it's got no argument being passed in, so this is when you don't pass an argument to useState, and here it's saying that the first argument of the tuple, or first member of the tuple, is going to be S or undefined, and the setState action is going to be S or undefined as well.

03:51 So yeah, like, it's interesting that useState is kind of built this way, really. I think it's to ensure that this case works really well, that when you do actually pass in a default string, you're not going to get undefined in the signature at the end. But I wonder whether that's the best approach, at least.

04:09 But there is one more approach I want to describe here, which is you can, in this second overload, you can say maybe return string and have this default string be optional here, so either string or undefined. This means you can actually pass undefined inside here, whereas here it's actually not

04:26 permitted, which is a slightly funny little wrinkle, if you notice it. Argument of type undefined is not assignable to parameter of type string. So I'd say the second solution is actually a little bit better, and just means that you have this overload. You notice that, by the way, the overloads are hit in the order that they're declared.

04:43 So if we had this one down here, then we'd actually end up erroring, because what we're doing here is we're actually passing, like, this maybe return string. A cool trick here is you can actually command click on this to see which overload is being triggered. So this one is triggering this overload, the top one, and this one is also triggering this

05:02 overload on top. So that means then that this example one is still string or undefined, because it's corresponding to this. Whereas, if I move this to the top, now this one corresponds to the top one, and this one corresponds to the second one. Beautiful. So that's a nice little trick to make sure your function overloads are all matching up

05:23 properly. But there we go. This is a kind of intro to function overloads, using them to increase our understanding of how useStates overloads work.