Advanced Generics 9 exercises

A Workaround for The Lack of Partial Inference

Here's the starting point for the makeSelectors function:

export const makeSelectors = <
TSelectors extends Record<string, (source: TSource) => any> = {}
selectors: TSelectors
) => {
return selectors

Currently, it's defined with two type parameters, `TSour

Loading solution


0:00 Let's start the refactor. We know that we are going to want to call this with our desired API, so it's a good place to start here. This will give you some proper errors here. This expression is not callable. Ooh. Terrifying.

0:15 We know that we're going to need to add in another function here. Now, makeSelectors, it's basically like we're passing in the source here. We've got two type arguments on the first function and no type arguments on the second function. This isn't right. We actually want to split them out over our two function calls.

0:36 We're going to say TSelectors, this one, actually belongs on this second function call. It belongs there. Have I got some syntax wrong? Yes, I have. I had a Record string string, and I wasn't doing the closing brackets there.

0:54 I can save that. Now things seem to be working. That's pretty amazing. We've got makeSelectors here. Now the first function call, if I split this out...Let's say, "firstFunc." Then we can call const selectors equals firstFunc.

1:10 makeSelectors, what it's doing, is it's just basically saying this is the spot where you inject the source type argument. We've got TSelectors extends Record string. We're yet to infer the argument of this, basically the selectors.

1:28 Next, we have this firstFunc. Then this one is inferring the type of the argument that's being passed in. We've got the type argument there. We've got the real argument there. Super-nice. The selectors, we end up with fully, strongly typed versions of this.

1:44 If we change the source, for instance, if we change this to id string or something, then this source is going to change too. This basically types the arguments of all of these sources here.

1:57 What happens if we don't have this? Source is going to get inferred as unknown. For people using this API, they're going to go, "Why would this happen? Why is this going to be unknown?" I'll show you a little trick here.

2:11 What we can do is, because this TSource can be anything -- it can be basically anything we want -- we can give it a default value. We can say TSource is "makeSelectors expects to be passed a type argument," which is mad.

2:29 Now, if you hover over this, you see source is "makeSelectors expects to be passed a type argument." We're getting a thrown error here, almost. Now we can say pass in the Source. Now that message goes away because we're overriding the default there.

2:48 If I bring this back here, then we can see "makeSelectors expects to be passed a type argument." We can grab that there too. It's super-nice. It's almost like you're able to throw an error just in the middle of this.

3:00 Then if we collapse this firstFunc, let's...Whoops, whoops, whoops, whoops, whoops. I'll say, "const selectors = makeSelectors Source" and then call it with this function. Now we've got the API that our heart dreamed of.

3:18 It's still a little bit ugly. It's still a little bit ugly, but you should be able to see why we're having to do it this way. We have to split out the type arguments over two separate function calls because we're passing one and then inferring the other.

3:33 If TypeScript supported partial inference, then maybe I can come back and delete this entire exercise or rather just show you that it works because yeah. I'm not going to say complain to the TypeScript team about it because I'm sure there's a reason why they haven't added it. My God, it would be very, very useful for these types of cases where you want to both pass and infer type arguments.