Mastering advanced concepts in TypeScript

Rate this content
Bookmark

TypeScript is not just types and interfaces. Join this workshop to master more advanced features of TypeScript that will make your code bullet-proof. We will cover conditional types and infer notation, template strings and how to map over union types and object/array properties. Each topic will be demonstrated on a sample application that was written with basic types or no types at all and we will together improve the code so you get more familiar with each feature and can bring this new knowledge directly into your projects.


You will learn:- 

- What are conditional types and infer notation

- What are template strings

- How to map over union types and object/array properties.

132 min
06 Nov, 2023

Comments

Sign in or register to post your comment.
  • Parmar Tarun
    Parmar Tarun
    SUNY Binghamton
    Hi, when can we have the recording available for this workshop? Thanks
  • Scott Huffman
    Scott Huffman
    Fidelity Investments
    Not sure if anyone is monitoring these comments for questions--my company has disabled access to zoom's chat--but I wanted to ask about this KeyToGuard, and whether there is any ways to break it into smaller, different types to improve readability?

Video Summary and Transcription

The Workshop covers advanced concepts in TypeScript, such as strict types and type guards. It explores the use of development tools and the advantages of using TypeScript in software development. The Workshop also delves into mapping union types and object types, as well as keywords and conditional types. Bug fixes and type validation are discussed, along with recursive parsing and entity validation. The Workshop concludes with a final bug fix and key takeaways for using TypeScript effectively.

Available in Español

1. Introduction to TypeScript and its Advantages

Short description:

I'll show you some advanced concepts from TypeScript to make you more comfortable using types. We'll discuss advantages of using strict types, work on examples with bugs, and explore the preciseness of types in APIs.

How to use TypeScript to hire employees in a web group How to use TypeScript in a VSC project How to alors create .sush I'd like to show you some a little bit advanced concepts from TypeScript just to clarify some things and maybe make you more comfortable using these types and see the value in them.

Today I'm here with Andrey from Content.ai. He'll help me manage the questions and chat. So if you've got any questions, feel free to ask. And ok, yep, that's me, you can contact me for example on GitHub you can see some things I work on. at the at content by a company profiling it up as well and okay I was gonna be on the other agenda we're gonna start by discussing some advantages of using the strict strict types like the strictest times that are available for the situation.

Next we'll work on the first example where there will be a small bug in an application that I've prepared and we'll talk about how better typing or better use of the Typekit type system would prevent such a bug and during that we'll uh we'll try out some of the some of the typeset concepts then we'll go on to another example and then depending on the time and maybe our energy we we can try the bonus that the bonus stuff that i've prepared that's a little bit more advanced more about playing with typescript and stuff like that so we'll see if if you are interested in that so at first uh i'll show you the application that we'll be working on it's just a simple events application that manages some events you've got you've got events maybe just a little bit i think i've got the buggy version right now running So, I'll run the other one.

Yeah, the application itself. You can maybe just to stop that you can download it from this URL. I'll copy paste it into the chat so that you don't have to find it look for it in the on the screen it's all the other how great thanks so thank you for that and now the application should run smoothly and as I said it's a very simple application that manages some events, you can click on the event, there's a description, their names, I can create a new event, I can select an organizer and it's there. I can delete events, I can create events for a certain organizer right from here, and I can copy events. So very simple. And yeah. So let's continue with this.

So, why would we even want to bother with with types and type system. The thing is, I often see that people think about types as their enemies, something they have to combat with and there are always obstacles in their way. But to do this, type system is there to help you. It can find a, it can catch bugs. It can really catch a lot of bugs And it's there to have your back in case you do some typos and mistakes, this is very often. And can also help you explore APIs. Like, often if I start with a new library, I just type some things, some function names and stuff like that, and I look for types, what the IntelliSense, for example, offers me, in my code editor.

For example, if I open this example file, you can see a function, write by reference that accepts an object, a reference, that has two optional properties, ID and external ID. And now from this API, I say okay, I'm going to call the function, and what should I provide the function? Well, I have no idea. Should I call it with an empty object? TypeSketch is fine with that. The TypeSketch is not able to advise me because according to TypeSketch, this is fine. But it probably isn't fine because the reference is just an empty object. There's nothing. Probably what the author really intended to do is to say, okay I want you to provide either an ID or an external ID. That's probably what the author meant but he didn't provide me any exact guidelines for that. So I'm lost here. But if he instead did something like this like this, okay, I want a union type where I want the reference to be an object with one property id that has to be there, or another, or a a different object that instead has a property external ID, that's not optional and always has to be there, now, the type key tells me, okay. MT object is not a valid parameter for this function. You either need to provide an object with ID property or external ID property, but either one has to be there. And at the same time, if I say... This is fine. That's disappointing. I thought he was going to tell me that I can't provide both of them, but well, that's not the case. But at least I know I have to provide one of them. So in this way, TypeScript really helps me to explore the API and see what's available and what I have to or don't have to provide. It's really much more clear even if you read the type, you see one of them is available. Or one of them is necessary. uh regarding the preciseness of types uh you can you can also often see a function like this a function takes some string that's uh that uh has to repre that represents some kind of action you want to take for in this example uh you want you want to create an event of a certain type and then you switch on the type and you provide different kind of event for each type. And then if you receive an invalid, some kind of unknown event type, you just throw an error because that's an invalid state for you, right? And then as a user of such an API, I'm going to go and say, okay, I'm going to call this event type and I have to provide a string. Okay, what kind of string? I don't know. I'm just going to slap there some string. and it's going to throw an error because this is neither of these strings that are used by the function. Again, if the author of the API would instead write something like this... and now, I've got an error. and the typeskip tells me, okay, you have to provide this or that. Pretty clear.

QnA

Exploring Typeskip and Development Tools

Short description:

Typeskip helps catch bugs and explore APIs. Integrated development environments and TypeScript-capable editors are needed. The app has a bug in creating events from existing ones. The theme is affected by browser settings.

So this is how typeskip helps you catch your bugs and explore the APIs. So you really should instead think about typeskip as your body that sits behind you and tells you what to do and what not to do.

Okay, with that, if you don't have any questions, we can start with the first task that we have prepared. If you have the application running, again, as I said, it's just a simple Next application created with the Create Next app. Well, the only command if you have you have the application cloned on your computer locally, you just you just run an NPM NPN CI and to install your NuN modules. And then NPM run def to start the application in development mode. It should start on the 3000 local local host port. Maybe we can give people some time to actually clone the repository and run the project locally, if someone needs the time.

And there is one question for you. What are the integrated development environments or other software tools required for developing the project app? So is it enough if people just clone the repo and run MPMI? Well, you should have some, some editor with capabilities to develop TypeScript project. Essentially what you need to do is, when you have the when you have the editor open, open, then to see to hover over some identifiers and see the type of the identifier. we are going to work with a lot of types, so this is quite essential to see the different types of your identifiers. For example, in the VS code everything works by default and no need to worry. If you've got any other editor, you just need the editor to be able to work with TypeScript as a language server. for those that don't know the ci command is quite similar to the install command it's just install packages according to the package log so you're gonna have to you you'll have the same version of dependencies as i have which will make debugging a lot easier in case there are any problems so if you if you have the application running you should see something like this apart from these two events that I added just a few moments ago and the first issue I wanted to address here is that you can create events, you can create events from here and here you just fill in the event name and the event will automatically be attached to the respective organizer that has the icon attached, but if you want to create an event from an existing event, it should not work. But it does on my machine, apparently, because I've got the bug free version running again. it. Okay, this should be the buggy version. Let's try it again. And it doesn't work at all. I'm going to clean my local storage. The app saved the events in the local storage. So that's where you can clean everything up in case you run into trouble. But yeah, I'm here back again. And if I want to clone an event, you see now it doesn't work. Now the event name is not filled in here, but it should be. So let's go into the code and see where the problem might be. If you open up the structure, everything we will be interested in this session is in the Source folder. There's an old encode. a couple questions about the versions because the theme seems to be Can you repeat that? Yes, can you hear me? Yeah, now. Okay, I'll come closer. So it seems that a few people including me are struggling with the theme of the app. Do you have to have a dark mode and browser enabled in order to see the same theme as you have? theme of the browser theme of the of the app because i'm seeing a different background than you have okay uh that's interesting uh the the styling is completely done by the create next app command okay so it seems that the app the styling takes your browser setting into account so if your live steam it will be light if your dark theme to be dark uh i am not sure if if it works properly in the live team i didn't try because i didn't know that it has such cool it seems to have the same behavior in firefox and chrome as well so it must be the the light theme um it does the same thing for me but the app works so uh if If you have it and you use it lightly, just, yeah, we have to cope with that. Otherwise, it seems to work. The other question here was if there is another branch where there is a working version, and if people can switch between the bug free version? Yes. I didn't commit the bug free version yet. I thought I'm going to commit it at the end. But if you want to browse that now, I think I can commit that as well into another branch. Right now there is no such branch. Okay, cool. So everyone has the bug-including version? Yeah. Yes. Perfect. And the last question here was, what are development norms and standards adapted to the project lifecycle development process for building the app program's code and data structure? I think that's a bit of an advanced question. I assume that you just use the next JS sample right? So I guess you can find your answer there Mohamed. Otherwise we can pick it up at the end of the webinar so that we don't get stuck up on that right now. Okay, I think that's good. We can continue. Good. So okay.

Understanding Type Guards and Their Advantages

Short description:

The app structure includes a folder with modules and TypeScript types for entities. Generic Type Guard library is used to create type guards and retrieve the type of the entity. Type guards are functions that return a boolean at runtime to check if an argument is of a specific type. Type guards are commonly used for system boundaries where data types are uncertain. Type unknown is used to assert that an external input is of a certain type. The Generic Type Guard library provides utilities for building type guards for object types. A utility called isInterface is used to create type guards for object properties. The makeAdditOnly utility modifies a type guard to return a read-only type. Declaring types in type guards allows for flexibility in removing properties from types without affecting the type guard. The garbage type utility type is a type level function that transforms the type of a type guard. By applying the type of operator, the type level function accepts the type of a type guard as a constant value.

The app structure if you're gonna take a look, then there is a folder with modules that there are some TypeScript types of some entities that we work with in the application. You might notice the definition is is kind of strange. It's because I used a library called Generic Type Guard to create type guards for the entity and at the same time from the type guard get the type of the entity actually. I don't know if you know what type guard is. It's just, it's a function that during the write time, runtime returns a boolean, so either true or false. And for the type script, it provides the information if the provided argument is of some type, or is not. The type should be subtype of the provided type. So, for example, I can have a, we can have a we can have a type guard that receives an argument of type boolean and i'm gonna say okay if i will if i will return true argument is actually type through not type boolean but type through you see in a in type script in TypeScript, something can be of type true and through is fine. False gives me an error. that boolean actually is just a union of type true or type false, something like this. So I can I can have a I can have a type guard that says, okay, I'll see if an argument. And in the written type, I will say, okay, the argument is some some more specific type of of the type I've received. And then the implementation of the function is really up to the implementator. I can just always return true, which is obviously not a valid implementation. Or I can say something like this, which would be a valid implementation. So this is a type guard and obviously the type, So, one of the common shapes of type guards is that you receive an unknown, because one of the most common use cases for type guards is boundaries of your system. When you write on your application, you have some boundaries. For example, you fetch some data from an API. The data you received from the network is some data you really can't be sure what the type of the data is. The JSON parse function will return any, because it's just a string that you parsed and you are not sure what's in it. So this is something from the data comes from outside your boundaries, outside your system That's inside your system. Everything is typed and perfect and beautiful. And you, you can be sure that what you receive is what the types declare that it should be, but from outside our system, you can't be sure about that. So, uh, if you receive from something outside of your system, it, it should be of type unknown because type unknown is not assignable to anything. It's something that. that you first have to use type guard on to assert that it's of a certain type. So if I rewrite my type guard, it will be this type guard that accepts unknown instead. I'm gonna, it will work the same, but I should just rewrite my implementation to something like this. Irko, could you make the font a little bit bigger? Of course. Thank you. Can I remove the… You can use the shortcut, you can click on top and then use the command plus shortcut. But the pointer should be away from the page itself. you can click somewhere outside the page yeah okay thanks is this enough beginner or should I okay so the proper implementation for this type guard would be something like this right because we first need to assert at runtime that it's really a boolean because we don't know what is going to be, it can be object, it can be a number, it can be null, undefined, whatever. So this is a type guard. And the type guard, it guards the argument that it will be of a certain type and like basically the generic type guard library provides a set of type guards and set of utilities for building typeguards for example here there is a utility is interface that provides a typeguard for building typeguards of an object type here we specify typeguards for different properties we will say that okay the type that we will receive should be an object object it should have a property id that should be of type string and it should have a property name that should be also of type same. And then we're gonna call the function get that just makes the, creates the type guard in the end. And in the end, there is a small utility that I wrote that basically modifies a type guard to return a read-only type, because I often like to work with a read-only types and an immutable code because, is just easier to follow at least for me. And again, this make-addit-only, is a pretty simple utility that modifies an existing type guard that transforms an input into an output or assails the sum input type is actually of output type and modifies the type guard in such a way that it also, it not only assails that it's an output type, but it also is a return output type. And what is the advantage of declaring the type like this? Because if you look at the organizer type, it's exactly the type we specified in the type that, but we can also like, you might say why we don't write the type directly and then write the type guard apart from this, ourselves. Well the advantage of this is that we actually specify what the type should have in the type guard. And because TypeScript is a structural typing system, if I would write a type, for example, with properties ID and name. Then I write a type guard that asserts such a type, and then afterwards, I would come back and say, okay, I actually don't need the property name for some reason because I don't like it. I would just remove the property from the type itself. Then I would have the type guard next to it. I would not be forced to remove the checking of the property like I can actually show it it would be easier right and now this is this is a type guard of my organizer type and now if I say okay I don't want this there is no error the type guard will still check that the the name is there and will fail if the name is there but it should not it should not fail but it will because the typeskit will not enforce the typeskit will not enforce the type guard to check exactly the type it can check some type that that's wider that has more properties that's not what want here so in what I instead did is they find the type guard instead directly and then infer the type from the type guard this garbage type utility type it's you can think about these utility types as sort of type level functions, they accept a type and then they return a type. So, basically they transform the type. They've got some inputs and some outputs and both are types. That's called type level function. And this type level function, it accepts a type of a type guard, which is this. And because this is a runtime level value, it's not a type, it's a constant, we need to apply the type of operator that gets the type of the runtime value.

Exploring Type Guards and Bug Fixes

Short description:

We explore a utility that extracts the type guarded. We have models, data storage, UI components, and a bug in the event creator component. The bug relates to a variant of type string and incorrect comparison. We fix the bug and encounter more errors in the events listing. We investigate optional properties and improve the type definitions.

And this utility just extracts the type that's guarded. And we don't have to define it manually, and then there is no problem with the structural typing, because now, if I remove the name property, it's no longer in the type guard as well.

Anyway, So this is just a short sidetrack into the typeguards. So we've got a few models here defined using typeguards, as I explained before. Then we've got data. Here's where we store, actually, those entities in the local storage. There's the call to the local storage. There's a safe entity, ultimately, these remove entity. Standard crew to operation. we got a few UI components, nothing really interesting. And then in the app we can go under event creator and event listing. Actually the event creator might be of interest, because this is the component that handles handles this. We've got a form, we've got an input for the name. We've got a dropdown that's optional for only some variants, apparently. That displays the organizer field, this one. and we've got the Submit button. And where might be the bug? Well, this variant, it controls whether we opened the creator from this button, this button or this button. And the variant is of type string. And what do we do with the variant? Okay, we compare it if it's a from event, we compare it if it's a standard. if it's a from event okay so it's probably not any string it should be some some kind of union of string letters as uh as we export before so we should narrow the type so oh We know it can be a standard variant or it can be a from event variant. That's what we use here. Okay. Everything seems fine here. And now we've got an error in the events listing. Oh. Okay. And see the right from the error, you can see the bug We can because we use for event instead of Instead of from event No, one more here instead of from event that should that should be passed instead so we pass that in the invalid value and That's probably what caused the bug I'll try to correct that once I figure out where I am. Okay. Now it should be better. It seems to be the problem now. Okay. It also tries to send for organ for organizer. which is something that we don't have in our list, so we'll add it. Nice! And now we have no errors here. Let's try the application again. No, this one. okay now we've got a bit more errors so we probably didn't really fix this so let's dig a little bit deeper and we've got two optional properties organizer id and source event id They are both optional. It's always a bit suspicious when I see more optional properties and if we go, we see, okay we expect that if we are in the from event, we want the organizerId and the source eventId to be specified. probably don't want to put undefined in here, into the key that's used to save the entities. So this is the first problem, when the from event variant is used both of these should be required, not optional and if you use for organizer the organizer id should probably be defined as well so we will define a better type for this the type should probably be a union because we want we want different types for the probes for different variants so we'll start with defining we'll start with defining what types should we expect for each of the different variants and then we're going to connect those variants into one union type so we'll define a map because what it really is is a map from one kind of variant into into an object type that should be concatenated into the remaining props. So define something like perVariantProbs, because those are different props that are expected for different kinds of variants. And we'll say, OK, the first variant is per event from event. Right? Yeah, from event. Okay. For the fromEvent type we expect an object with property event id, that should be your type string. And, organizer id, that should also be your type string. Right. So, we've got more variants. And we also have the for organizer, right. and this expects also an object with organizerId property.

Exploring Mapping of Union Types and Object Types

Short description:

We explore mapping of union types and object types. We create a test using the key of property on the per-variant probe. We transform the union type into a union of objects with relevant properties. We leverage distributive conditional types to iterate over all types in the union type. The result is a union of objects with the variant property and the relevant properties for each variant.

We've got the standard variant. The standard variant doesn't really need any other properties. So we can say it expects an empty object. Now we defined what other properties we expect for each of the variant. But we need to make this, or we need to turn this into a union type, that we're gonna concat into the props object, because the props is an object type.

So what we can do is, because there is one of the things that we are going to explore in this example is mapping of union types. And picking apart kind of the object types. Because what we can do, we can write a test here. And if we use the key of property on the peri-variant probe, however, this it won't tell me more. Typeskip is not really helping me, it just says it's a keyof of the variant probes, but what is really the keyof of per-variant probes, it's a union of string from event, for organizer and standard. The keyof property gives you a union type of all the keys that are in the specified that you pass to the operator.

So, this is a union type of the defend keys. What we want to transform this union type, we want to transform it into a union of objects that have all these properties for this the relevant variants and also a property variant that has on only the one specific key available in the type something like okay what what is our result what we expect we expect something like this uh i will omit the rate only keys because it only type because it's just confusing. But the result is something like this. This is the first variant. Now let's continue with the second variant. Finally, let's add a table with the values of 1, 2 and 3. This is the result that we want to achieve. If we have this type, we can just remove these three properties that are not really that useful in the API and concatenate, and we can concatenate the type into the props type. And now, once we have this, the props type will narrow the type of the remaining properties based on the variant property that we'll supply. We'll see that in action in a moment. So let's try to do that because we don't want to hard code this. We want to instead use this utility type. we can leverage one property of conditional types. In the documentation it's called distributive conditional types. And the property is that if we apply a conditional type onto a union type, the condition of the conditional type is applied separately for each of the type the union so here you can see it as an example if you've got a two array conditional type that checks the type if it extends the type of any it's all it's it's always going to be true it it changes the type to be an array of the types so it's just a kind of two array utility and if we apply this utility on a string or number we're gonna get string array or number array not string or number array that's uh that's the distributive uh and that's the distributive uh conditional types property and we can leverage this to iterate over over all the types in the union type and transform it in a way.

So we can say, okay, we've got key of of our map of variance. And we're going to say that, okay, does it extend any type? If it does, let's create an object type we can make it right on name and okay what what are going to be the properties one property we want is the variant and it should be of type what should be the type right we can use a keyword infer the keyword infer tells us or it does the typescape to put the result of the of the of the type that that he found or it found here into the into the variable t so in the t variable we should have the each of the types in the union type separately so on the first iteration we are going to have their the from event on the second iteration it's going to be for for organizer and then the standard so that's exactly what we want for the variant property so we can assign it there and then we've we've got this property but we also need these properties so we're gonna convert them into the type and what we wanna really connect into the type is what we find under the key. So for event it should be this type and we can get that using this notation of square brackets. And since it's a conditional type, it's like a tanner operator for each of the question mark, we also need the other branch. We're gonna say it's an ever type because we don't expect to ever fall into this branch. Okay what seems to be the problem, okay, the problem is that the type skid doesn't believe us that the T type is really some of the keys of the perivariant props type. So we need to assure him that it really is this type. So we're gonna say okay. Okay. We're gonna say typescript, infer the type T from this expression and also check that it's a key of the pair-variant props. Right? And now what's the result? The result is a union of an object with the variant in a key of props type so this is nice. We can see an example where the distributive property didn't kick in because our type is an object with a variant that can be any key from the per variant props and then a union type is concatenated from all of the values from the per-variant props. That's not really what we wanted. And the problem is that we apply the conditional type on this expression but the distributed property of the conditional type is only applied when we apply the conditional type on the type parameter alone without any expression around it. So what we need to do instead is to make this a tiny bit more complicated and we are gonna say okay infer the result of this of this expression into the into this into this variable and then they are going to do another another like a condition or a conditional type that's nested in the previous conditional type and we're gonna say okay if the if the T type that we inferred previously extends any type which is always to rule we we we don't do this conditional type for the condition itself we just do the conditional type to get the distributive property of the conditional type just just to iterate over the union type. We are going to say if the t extends any really, create this type, otherwise, again we don't really care about the other branch. And now we get what we wanted. Now we get a union of one type. It's kind of hard to see. But you see there are there are braces, brackets here, and the type is object with property variant that's only the from event string. No other string. And the properties that are related to the variant from event. And another type in the union is a variant four organizer that again has only the relevant properties for the four organizer variant. So this is actually the same as the result.

Explanation of Keywords and Conditional Types

Short description:

We can rename this and concatenate it into the props, eliminating the result variable. The use of 'never' in conditional types eliminates certain types from unions. The 'read-only' utility type can be used to clean up types when combining them. The 'infer' keyword extracts the type from the left-hand side of an expression. The 'extends' keyword represents a condition in conditional types. The 'key of' expression returns a union of all keys in an object type. The 'info' keyword extracts the type from the left-hand side into a property. The second 'extends' keyword restricts the condition in the expression.

So we can rename this to something like something better to read than test. and concat this into the props instead and now we can get rid of the result and we specify the props in a nice way in the in this map we transform the map automatically using this type level type of function of sorts and then we apply this into the props

The first one is why do we use never as the false case in the conditional type? Should we always use never when we use infer? Never is kind of common because the thing with never is, if you have a union with some types and then the never type for example number or boolean or never typescape will automatically eliminate these types because the never types are something that's never going to happen like for example a function that always throws an exception like it never returns anything it always throws an exception that's a function that returns never because that's something that's never gonna happen. So that's why the never is useful in these kind of examples because it, for one, it really illustrates something that's never gonna happen and, for another thing, it's gonna be eliminated from the union types. Okay hopefully, yeah, that's clear enough. Thank you!

And the other question is, can you clean up the type by using read only when combining types? You can use the types clean up the type So when you're combining types if you can use read only I'm not sure not really sure what you mean one by cleaning up the type the what the read only type or what the read only utility type really does is so uh so this is this was questioned by Massey and he explained that it's online and 74. Okay, I'm 74 only rather than 37 to 81 Okay. I'm still not not quite sure what what he means by the In per-variant props, could you not use read-only and then only use it on line 74. Oh, okay. Now I understand. Yes, yes of course you can do that. You can do it like this as well. Perfect.

And one last clarification, we have another question about clarification. Vera is asking if you can go through the whole expression and explain what each keyword is doing. So maybe like a summary or a wrap-up of this to quickly explain what each of these keywords is doing because it's a lot of them in there. Of course. Your problem in this expression. Yes, especially the extent in a first situation. Yeah, I think that's the one that you're looking at. Yeah. Yeah, obviously. So the first is the key of expression. We already went through this. The key of expression takes a type, an object type, and returns a union of all the keys in the object type. So, in this example it's going to be only a union of a string from event for organizer and standard, right? The extends keyword is a keyword from the conditional type, it's probably clear here in the official documentation where you see it represents like a condition. If the type on the left of the extends keyword is a subtype of the type on the right side of the extends keyword, then the result of the whole expression, you can think about this like a temporary operator in the standard JavaScript. So if this holds, then the result of the whole expression is this type. Otherwise, if this type is not a subtype of this type, then the result of the whole expression is this expression after the call colon, no, something like this. I'm not sure what was the name of this operator. Nevermind. So this is this extends keyword. And then on the right hand side of the extensor keyword, instead of this any, we've got this expression, no, this expression, all right. And this expression has got quite a few of the keywords, so let's break it apart. This part is the info keyword, as I said. So it tells the typescape, give me the result from the left-hand side type, from this type, into the property. So, it can be nested. If I say like, the type needs to be of shape A, like this. Then I'll say typescript, The type on the left hand side of the extends operator needs to be an object. The object needs to have a property A and the type in the property A, please give it into the type T. So the type T will hold the type of the property A inside this type, like if I would write it like this. And if I write it like this, the typescript will give me the whole type to the t that's what i want i want just an alias for the for the type of the expression in the left-hand side so that's that's the infer keyword that's a little bit more tricky i would say and then we've got this part like this this is basically it's the same word as this extends that that's that's a bit confusing but it plays a different role here here the role of this of the second extents is uh is to restrict the condition like i i with this with this extents i want to say that this type needs to satisfy this condition it needs it needs to be a subtype of this type again here it is confusing because those type those types are the same. So it's obviously true, always true, but the TypeScript is not able to recognize this and TypeScript will not allow me to index this object type with the ttype I just inferred because he is not aware that this expression is the same as this expression. The TypeScript just doesn't see this. So I need to put it here after the extends condition so after the after the extends is just some kind of condition i want to place on the on the input type i hope it makes a little bit more sense yeah i think that that's really good one note i would add here is that for each of of the stages of the expression, you can actually hover over the type and see what kind of results you get, right. So that's what helped me a lot with the key of type of magic that you can always assign it to something and look what it actually created. I hope that this made sense. There is one more question. Can you please explain the purpose of using the infer keyword within the type definition of test? I don't know where is the type definition of test? Oh, it's probably this, I renamed this. It was test, I believe. Yeah.

Explanation of Infer Keyword and Type Validation

Short description:

The infer keyword is used to assign the specific variant into the variant field or property. It triggers the distributive property of conditional types to iterate over each of the variants. The expression helps define the per variant props comfortably. A type level assertion is used to fix the issue of concatenating a string into the props, ensuring it is an object. The record type is used to validate the type and assert the condition that the value needs to be an object with any string keys. These lines ensure valid values for the different variants.

How does it help in defining the types for the variant property? It seems you are lagging, you've got connection problems. Oh, sorry for that. Is it better now? Yeah, yeah. I can hear you now. OK, OK. So did you, should I repeat the question? If you will, please. Yes. So can you explain the purpose of using the infer keyword within the type definition of test? How does it help in defining the types for the variant property within per variant props? Yeah. This infer type it's only needed to, like I need this to assign the specific variant into the variant field or property. And I also need it to trigger this distributive property of conditional types, like to iterate over each of the variants, I need the type to be alone. If it's within an expression like here, I'll use the key of, like the expression I use for the conditional type is this one, then the distributed property iterating over the different types in the union won't work. I need it like this, just the type and then the extent. That's why I need the infer to put this type into a variable or put this expression into a variable. And how does it help with defining the per variant props? Well, this whole expression helps with defining the per variant props because I need to define them comfortably like a map here and then transform it into the union type instead of writing the union type by myself. I can then go even a bit further because now nothing forces me to put an object here, right? I can put just string. And then that's gonna be a problem because I'm gonna concatenate a string here into the result. and then I'm going to concatenate it into the props and the props should be an object. It cannot be a string or a boolean or something like that, but we can fix that, let's leave it like this. We can fix that with a type level assertion. We can say, okay, we've got a type there's going to be a type level function. That's it. We will assertion function. It will accept some type, some type P and we've got another, another use of the keyword extends, but this, this time it is similar to this usage. This extends tells the type script that there is a, condition on the input type of this function. It says that this t needs to be a subtype on the type on the right hand side. But the type any is not really useful because everything is a subtype of type any. So let's put something more useful here and we actually need this type because let's think about this what we really need we need we will we're gonna we're gonna pass this type the whole type into the validation function because we want this type to be validated and we want this that with this type to be an object with some keys whatever we're going we're not going to restrict the keys the keys can be any type of string string but the value needs to be an object. That's really the condition we want to assert here. So let's write this into the requirement, we're gonna use a record type, record type is a type of object, the first parameter of the record type is the type of the keys. The The key can be an string. We don't really care about the string, but the value needs to be also a record. The values of the record can again be any string. We don't really care. And the value of the different fields in the inner record can again be anything. as long as the whole value is an object we don't really care what's inside. There can be a string, there can be a boolean, it can be whatever. So we're gonna say it's unknown, it can be anything. And we're gonna write the right amount of these angle brackets and then we're going to say, okay, that is, all is the type T. As long as the type P passes the condition that we, that we applied on the, on the type in the input, we are going to just return the type. If you didn't get it, and then And then, if we apply the type , If got an error, immediately have an error because the type response does not satisfy the constraint. That's applied. The constraint is this type. This is the constraint that we apply on the input. And this this type doesn't satisfy the constraint because the property standard has an incompatible incompatible type in itself because it's a string and we need it to be an object of string as values and unknown as string as keys and unknown as values if we put a record here empty record now everything is fine so these two lines will ensure that we will always use valid values for the different variants, and now we are safe. And to test this, we can go to the event listing. We've got an error here immediately that tells us that the type from event is not assignable to type standard. And that's because we don't really provide the other properties here. just provide the variant but we don't provide the organizer id or the source event id so we need to fix this and we can fix it with an expression like this The sidebar probe holds the properties that are used for the sidebar. As you can see, the organizer ID is always there, but the event ID is sometimes null, oh now i'm gonna because if i want to create an event for an organizer but don't clone and clone an existing event i don't need an event id in this in this case the event id will be now so we're gonna check what's the type of the event id if the type is the string so it is provided we're gonna pass ID, and the mower's event ID. Otherwise, if the event ID is not provided through the sidebar probes we're just going to pass in the ID and one more thing we need to do is to help TypeScript understand the relation between between the variant probe and those other probes that we provided.

Fixing Bugs and Parsing Input Strings

Short description:

We fixed a bug related to providing the source event variant and property. We learned about using union types and transforming them. We encountered another bug related to a typo in the key. We fixed the bug and discussed preventing future bugs by parsing input strings and checking key parts. Template strings are useful for composing strings and creating type-level functions to parse types.

To do that we're going to provide the variant here you're gonna say that this branch is a for event from event variant this branch is before organizer virion now we can remove this and the type skip is happy that skip is happy and we provided the proper proper prox for each of the variant the typescript it doesn't force us here to provide the source element, but it forces us here. If I were to delete this branch, I immediately have an error here because I didn't provide the source event variant, source event property, when I have to provide it. let's see this now it works now we fix the bug is this clear

It seems there are no questions, so we can move on. So, we have successfully fixed this bug. So, let's see what are the key takeaways from this. We have learned how to use union types to capture the reality, and help us or help the the app to be more resilient to bugs, and catch our bugs when we do have some mistakes. We learned how we can iterate over union types and transform them into another union types. Like if we'd apply a map onto an array. And we have learned that it's kind of easy to transform one type into another type, and Like play around with the types a little bit. So we can move on to another bug that's present in the application. And if you again, if you've got any questions, just throw them in.

And okay, let's see another bug. I will create another event here. and let's try to delete the event and it doesn't work and I've got a console error. And this seems to be the bug. So let's see there was an error thrown because the function in the storage is Okay. Data storage, Ds. He is. Here is the exception that I got. Is this again. And it said that for a key organizer slash some ID slash lens like some ID. it didn't find a proper typeguard. So, let's see. Let's see why the error is present. We've got an object for different entities that we want to save. The application supports organizer entities and event entities and attendee entities. The different entities also have relations between themselves as we can as you can see here that the event entity is a sub-entity of organizer entity. What this effectively means is that if we want to save, if you want to save an event, we will provide something like this, organizer id like this organizer slash organizer id slash event slash event id and what seems to be the problem here is that we've we have a typo we've provided organizer with an s instead of that so let's see And indeed, there is a problem here. And there is another problem here. Okay. And we have fixed the bug. And that's that's nice that we have fixed the bug, but we'd like to prevent future bugs of this kind to ever happen again because it's really easy to mess up the string that we expect on the input type right we don't want to do this instead what we can do with typeshift we can actually parse the input string input string that we that we receive and check that the each of the parts of the key, like the organizer and ID and the event, actually match what we expect it to be. So, that's a little bit more challenging, but we can do that. We can use a property called template string. With template strings, you can not only compose strings like this, Let's say, and let's see what's the type. And now the type is a union of all the options that I've provided into the template string. This is one usage of the template string feature. I can also provide another wild card here, so to speak. And the result will be the combination of all the possibilities that results from the work I provided. This is kind of nice, you want to provide a lot of different options for example if for some component libraries and stuff like that then you need to mix in a lot of a lot of possible types but let's see another usage of the template string property let's write a function that accepts some input type t that should be It should be a string. This type level function just accepts things. What it will do, it will check the type and see what, it will try to parse the type. Let's see. Something like this. We are gonna infer, We are gonna infer what is after the input dash part from the string. And the result, we are gonna infer the result into the res type variable. And if the input type T matches this, we are gonna return the res type. Otherwise we're just gonna never bother again about this type. So let's see what the result for something like this. And the TypeScript correctly parses just the type, just the part of the string that we are really interested in.

Parsing String and Recursive Parsing

Short description:

We can parse the string into different parts based on the input key. We want to generalize the parsing to any top-level key from a specified shape. We also need to recursively parse the sub-entities by calling the TypeScript function on itself. By iterating through the keys of the shape object, we can parse the desired properties at different levels.

So, we can leverage this to parse this string into the different parts that we are interested in. So, let's try to parse the string. We will accept the key as the input and we expect the key to be a subtype of the string type. Statement. I'm gonna parse the string and expect to be there. Organizer here, and infer infer the result. We're going to return the result and let's try to have the example key. and we correctly got everything that's after the organizer part of the key.

Okay, but this is kind of specific, and we would actually like to parse anything that's specified in this object type. We don't want this to be specific for organizer, then another for event, event, and then another specific for attendee, right? Let's try to generalize this a bit. And let's say we want to parse... We want this part to be any top level key from this shape. So let's say keyof typeof... Annapurv.dummy... And then then we expect a slash and we infer the result and again return the result I shouldn't have enough remove that this here, you have the result it's correctly parsed and for another test Let's try a different key. It's another valid key that we expect. There's an attendD slash ID. And this is also correctly parsed.

But now it only works for the top level properties and we would also like to parse the event part of the property. because right here we don't have the event id we just have the organizer slash event slash id which is not really what we wanted so to do this we need to iterate deeper we need to recursively go through the sub entities and parse whatever is inside and this is actually something we can do with typescript we can recursively call the typescript function on itself And we also need to change the object that we check for the keys because on the top level, we check these keys. On the next level, we will check this key and whatever key we add later. So we'll receive the object as an argument. It's gonna be, let's call this shape, shapes. And it should be a record of string keys, no not this, string keys. And what should be the value? Well, the value should also be a record, or rather an object with a guard type. let's say, let's not worry about this right now and the subentities Doesn't really matter, right, no matter as well so, let's format this to make this a little bit more readable All right. And now, instead of this, instead of hard coding the object, we are going to use the parameter that we've bought in here. Okay. And we don't know that the TypeScript is not really sure that keys of the shapes type are of type string. So we're gonna again. Okay. That's weird. That is weird, Okay, what's just to be the problem? Hmm, he really thinks that there can be Something else is in strings. Even though there should be only strings. Interesting. Okay let's try to work around this. Let's try to do it like this. We are going to save the keys inside another type variable. Let's say keys. And we're going to say that the keys should be of type string. Now that we have the keys saved inside the variable that is insured to be of type string, it should be possible to use it. So let's continue with the key. It should be extension of key slash a resigned message. We will take the result, verify the result, otherwise we don't really care. Now it works. and now we need to provide another argument here so the argument really is what we used before because that's we want to use the specification of the type and we've got never that's interesting doesn't really work here okay let's divide this let's see what where's the problem okay the problem is this part that script is not able are because because I used key instead of keys, right? Okay, now it works. And we've got the same result as before. You can even have another test for the organizer key to make sure it works for all combinations we currently support, and it does. But when we get the top-level key, we don't want to end here, we want to continue recursively to the sub-entities. So, let's try this.

Recursive Key Parsing and Type Inference

Short description:

We need to stop the recursion when we reach the final key. We only care about the current part of the key. The subentities should be of type record. We need to check that the subentities satisfy the desired shape. We can use another level of conditional types to achieve this. We want to infer the type of the key we encountered and check the sub-entities for that specific key.

With every recursion, we also need to stop somewhere. We don't want to recurse infinitely. So the stopping condition is when there is nothing else left. Just the final key and nothing else. Because for example here, you've got only the level condition. Here, we go one level deep, and then there is nothing else. And we also step by two segments. Basically, we eat one slash each time we go deeper. so let's try to encode this in the code and say okay if we have something like this if we have a top-level key then some string that's the ID we don't really care there's any string behind the slash then you've got another slash and some something else whatever that is that's something we need to continue on that's the rest of the key this part of the key is no longer of interest we don't really care about this part we now only care about this part so we will continue parsing this part of the key we put the rest in here from the shapes right we We need to pick the subentities. It appears we can do that because the subentities are of type unknown. That's not really what we wanted. So we're gonna say that the subentities are a record. Is it better? No, it's not. We will need to adjust that. We'll need to check that the subentities Oh, and I can't spot properly. We need to check that the subentities again satisfies this kind of shape. We can do that with another level of conditional types. Basically, whenever you get a problem with conditional types you almost always fix that with another level of conditional types. So let's say shapes. Let's check what the... Okay. There's one more thing. We want to infer what's the type of the key we encountered. Because the first segment can be any of the top level keys. the top level keys and we want to check sub-entities for the specific key that we found in here. Not all of them. So again, we need to infer this. Let's name it properly. And we say that we want to infer the entity that we found. The entity should be something from the keys, from the top level, of the shapes that we we receive.

Recursive Key Parsing and Entity Validation

Short description:

We check if the subentity is a record with the same shape as the recursive call. If so, we recurse with the rest of the key. We handle the case when there is nothing else in the key. We return the entity we found or handle the case when neither condition is met. Everything seems to be working.

Okay. There's no one and sesame, and now instead of immediately cursoring we're gonna check that shapes is on dansing. sub entity really is a record that has strings as keys and basically the same shape that that we want to provide for the recursive call. If it does, then we need to recurse. And we want to recurse with the rest of the key. And okay, we need to infer the result again. again, next, those are the next shapes that we want to provide. Let's close the next shapes here. And otherwise, again, we don't really bother about with the second one, because this should always be true. If it's not true, there's something wrong with this data structure. Okay. We always end up in the never case. That's because we actually need to care about the other case. And we need to care about the other case for this condition because this condition expects that there is always something else. But that's not always true. Sometimes we are at the end of the key and we need to end with something. And if you want to enter something, we need to use the other condition. So if we are at the end, the key should extend a different shape. It should extend something like this, please. And then some ID and then there is nothing else. This is the last entity that we found. This is true. We can return the entity that we found, for example, and otherwise, this is the case that neither this nor this matched. This should really be in there. And we've got event passed from here. This is the last entity we found. We've got organizer because this is the last entity and everything seems to be working.

Parsing the Key and Checking Type

Short description:

We successfully parsed the last entity and checked the key's validity and the desired type. The string itself is not important; we want to parse Shapes and select the Guard. The Guard ensures the received entity matches the provided key. We can rename this to 'key to guard' and get the guarded entity type.

So, since we've successfully parsed the last entity that we found, it should now be pretty easy to check that the key is valid and to check what kind of type we really want. Because this is really where we aim with all the parsing of the key. The parsing seemed unnecessary, but it is necessary to check that if I want to save entity with this key i need to provide the type that's parsed by this typeGuard which means an event type so what we really want to parse here is not not the string the string is not really interesting in it of itself but really We want something like Shapes, where we index this with the Found entity. This should be this object or this object depending on what we parsed. And we want the Guard. So, we'll just select this. And now what we get is the guard that checks that the entity we receive is of the type that matches the key that we provided. This information we can rename this to something more useful. Let's see, is there a rename symbol? It is. So it's something like key to guard. And we can even get the proper type. because this might be more useful than getting the guard itself, it might be to get the guarded entity, which will be more useful in a moment. So, here we can reuse this. And we can use the, we can use the library to get the type discarded by the cut. And if you use this here, this a little bit, we've got the die we've got, we've got the type that's associated with the specified key.

Infer Next Shapes and Improve Error Messages

Short description:

We infer the next shapes for the given entity based on the top level object associated with the received key. The entity can be event, organizer, or attendee. TypeScript doesn't allow overriding the original entity with an inferred type. We make the function generic to work with different types of keys and transform the key into the associated entity type. The type of the entity should match the provided key. We can improve error messages by returning a string literal with the desired error message instead of the 'never' type.

Yurko, one quick question. Where do the next shapes come from? The next shapes. Next shapes are what we inferred for the given entity. Like, when we go, we've got the shapes, this is the top level object or the object that's associated with the key that we received, and we infer the entity that's on the given level of the key. It's either event, organizer, or attendee. That's the founding. And we get from the object, we get the object that's associated with the entity, we get the subentities, and that's the next shapes. We infer that. Yeah, I think that was a question about it. I think that we just missed the line 81. That should be fine.

And one question, a little bit older, so I think that you already changed the code, but it's about inferring the variables, and the question is why did we have to give the inferred variable another name? Yeah, because Typescript basically doesn't allow us to the original entity or the original type with another type that we just inferred. It always needs to be a different time. It's not allowed to override it then. Thank you for that. I think you can continue. Thank you for the questions.

So now we've got the ability to go from the key to the entity type. And that will help us to safeguard this. For example here, we've got a function that accepts the key that can be any kind of string and an entity that we want to save associated with the key that we provided. And again, there is really no type safety here. We can provide any type of entity, we can provide any type of string key So let's improve this with our types. We are going to make this function a generic function because we need the function to work with different kinds of keys, but we need to work with the type of the key. So we need to assign it some name. That's where the generic functions help. So we are going to say that it. we want a key property or key variable and the key parameter is now not any string but it's a string of type key and we're gonna leverage this to transform the key into an associated entity type. And now the type of the entity should match the provided key. We can quickly try this. Let's say we want to say using the organizer with a very weird ID. Now TypeScape already tells me that I need to provide ID and name which is the type of the organizer object. real values are not really interesting right now and here if we hover over the usage of a generic function we can see the parameters that typescape inferred for us and we can indeed see that the types are correct if we were to provide if you if you were to do the same typo that we did before that's the that caused the bug before we're gonna immediately get an error because the type we used, the key to entity type, was not able to pass this condition because the first segment is no key from the keys of the shapes. The shapes are these shapes from here and the top level shapes only have these keys. attendee and organizer. And here, thanks to our typo, this part is no such key. So it's going to end up in a never case. To improve the error messages, so because the typeskip just tells us that the type is not assignable to type never and from the user perspective I'm not really sure Otherwise, they are never typing. What does it mean? So we can improve this. To improve this, we need to find the proper case where to assign this. And the proper case is here. Because we're going to pass the first condition, the second condition. And this is the false case of both of those conditions. And we can, instead of never, we can return a string literal and the string literal will contain the error message that we want to pass to the user. So something like... ...I found. And if we go to the error message now, I guess it's not the proper branch after all. So let's see. Put it here. But this should not be the proper page. no really this face well there's something something I'm missing you Oh, there's an error here.

Fixing Input Condition and Loading Entities

Short description:

We fix the input condition of the guarded type by adding another level of conditional type. The error messages provide explanations crafted by us. We can use template strings to provide specific error messages. The TypeScript type system parses the provided string and infers the proper type based on the different parts of the key. The same type can be used to load entities, providing properly typed results based on the string provided. The code can be extended to load multiple entities by adjusting the type level function parameters.

Okay. Yeah. Let's move this, let's move this inside for now, because we know, we don't satisfy the, we don't satisfy the input condition of the guarded type type, because we don't pass something That can a that's that's a partial type guard. We sometimes pass a string that's that's not really what what what's about it here. So if this will figure this proper later. okay and again we don't satisfy the condition so let's fix this with another level of conditional type You see the communication levels, differentitterals. Let's actualize it. Okay, no, there are no errors here. And we've got the error message here. Now, the TypeScript will instead complain that the object we provided is not assignable to some kind of string, which is weird. But the string in itself contains some kind of an explanation that we provided as implementers of the type. We can it's kind of a hack, but it can be used to craft better messages. We can even use. I wonder which case it is because I really think let's backtrack a little bit and find the proper branch. Yes, it's the proper branch. The problem was with the input condition. OK. And we can even use template strings here and provide the error that we found. Let's see. And that's the found entity. It is. TypeScript complains that he cannot find it. That's interesting. Mhm. Because we are in the false condition, right? Yeah, he wasn't able to parse that. Okay, so let's add another layer. whatever is the problem is that we are not going to get that info because we expect it immediately to be of type keys and in fact if if there is a problem it it's not of the type keys it's There's something wrong, right? So we're gonna infer that whatever that is. And the result is again, not really interesting. I guess, okay. And this case, this case means that it's not really of this shape that the slash is missing. So we don't really care anymore. We're just gonna say that the key is really invalid and we can't say what part of it is invalid. this type and now now it's gonna say out that the entity organizer is not a valid name so we precisely know what part is invalid. Oh, this is invalid as well, obviously. and if we were to add event another problem and now it's gonna complain that this is not assignable to the event type because the event type has a lot more properties that I don't really want to write here but we can use we can use something from here because this is a valid event let's paste it here whatever id and now this is a valid event and the now really what the typescript does which is really amazing for a type system it parses the same that we provide and gets a proper type based on the different parts of the string of the key. You can even make an error here and then you gotta say okay the awet property is not a valid name. So even the error messages are kind of helpful actually. Are there any questions regarding this? So it's all clear. And we can use the same type to load entity as well. It's just a matter of writing it down. an entity, instead of accepting another type we are gonna return it, like so. And now if we were to load an entity, and we were to provide a have a properly typed result just based on the string that we provided. So this is this is another part it can also be extended to load and multiple entities but we would need to because with multiple entities we provide different kinds of keys we provide keys like this where we don't wouldn't provide the final id. So, we would need to adjust the code because in this code we expect that there is always an id at the end. We would need to provide another attribute or another input to the type level function that we wrote. Something like that would be of type boolean. Actually this is a good analogy. You can think about the extents part of the type level parameters as type definitions for the type level function parameters. Because since this is a type level function, these are inputs to the type level functions and these are types for the type level function input something like this okay and we need to propagate the the flag that we received into the recursive calls and here in the final part of the matching where we match the final the final segment of the key we're gonna adjust this to only expect the ID if this flag is set to true, so something like oh that's not what I intended something like slash and a string here any id and otherwise an empty string because if we if if this is set to false it means we don't expect to the final id it can be an empty string so this starts to be kind of hard to parse but if you pick apart the the expression it really makes sense and now let's say to key to entity type you should also accept the parameter Now, if we say in our tests, for example, it's need this. And now this is the same result as before, because before it expected the final ID, and we said true.

Fixing Final Bug and Key Takeaways

Short description:

We fixed the final bug in our application. The takeaways from this part are: we can pass string literal types in TypeScript, iterate in TypeScript using recursion, iterate on union types, and assert values on a local level. TypeScript is not able to assert complicated invariances in the runtime part of the code.

And here it should be a problem. That's interesting. Let's remove this. Okay, so what seems to be the problem? We pass it around. We pass it into the recursive call. Okay. And in the check, we extend. Not interesting, OK, because I put it in the wrong place, yep. It should be here instead. This condition is there just to extract the name of the entity for the error message. Not useful. It's not useful for the actual logic of the type. And now it works. It also works, and if I put true here, I've got an invalid key found error. Because if I said true, I expect the final ID but I don't get the final ID. That's the problem. So with this type I will just quickly fix these. Expect the final ID in this part. Also expect the final ID here. But for load entities, the final ID should not be present. Type this. What's the name? Something like this. Something like this and here we pass pause because for the load entities for multiple entities the final id should not be present. Let's quickly check that it works and indeed it works. It returns an array of organizers and if i were to pass an id it's an error if i were to get events from a certain organizer i'll get an event list of event types okay we've got this working uh well the this is easy And with this, the save, save and load entities, the storage API, whatever we call this, is pretty, pretty damn good. And well typed.

Any more questions? There is just one question, Mirko. One question but a comment that says, that's from Natasha. That is to so many conditions, it's hard to read. But I assume that's experience thing you can read through it well, right? yeah, it is just all of us, well, it is hard to read, you will get to used to it once you play around with the times with the types a bit more as you said it's a little bit of an experience thing, but at the same time yet kind of a sad part TypeScript as language is kind of expressive with this type level playing around and type level functionality so you can do a lot of things but it's not really easy and the syntax doesn't help that much like you can't make it nicer with with the tools that typescape has available unfortunately cool cool yeah I think that otherwise we don't have any any questions here so I hope it's it's it's all clear as I said it's it is hard to read but there's no really other way and once you get used to it so I could parsing all the different branches of the of the conditionals you'll get you'll get used to So, we fixed the final bug in our application. We checked before that works. So, now I can delete all my events and so what would be the takeaways from the final part is that we can pass string literal types in TypeScript. We can do all kinds of things with string literals and you can leverage this information to do whatever you need in your applications. It's really useful. We can iterate in TypeScript like you would do in your standard JavaScript runtime code, only you don't have while cycle and for cycles and whatever you just have recursion. You only iterate to recursions and iterating on union types. That's a special kind of iteration. And yeah, we didn't go into this part. But the problem here is that if i for example in here okay it works but if i uncomment this part then it should to an error yep here I've got an error because here okay this we didn't really fix this key this this function this should really also work for a key and and later on, return the type guard that's associated with the key. And yeah, it should also accept the, boolean parameter. And this is gonna be a problem because it's really hard to connect the runtime part, the JavaScript, to these complicated recursive type level types. So usually what you do, you just override it. Here, it's still possible, you can do something like this. But but here TypeScript still isn't sure what's the result. What's the type of the result? So I would still need to override it. Like so. Yeah, and this doesn't really match because it's not always a guard. Yeah, we would need to adjust that, but let's not spend any more time on this. The real problem is that you need to assert this, instead of TypeScript knowing that it matches because TypeScript is not able to assert this complicated invariance in the runtime part of your code. So this should be one of the takeaways as well. But the thing is that you do this assertion value, like tell TypeScript what it really is, and basically don't listen to his advice. But you do this on a local level, in a small function where you know what the result is. But thanks to the complicated type that you wrote, you provide a nice API for the user of the function. function might be used on many different places all over your code base and all the places will be safer thanks to the types so it this is a small but i think kind of beneficial title and another thing that you might know that we also didn't go into because i forgot about that is the satisfy keyword here we've got the same problem as we did in the where was it creator in the perivariant props in the perivariant props we use the asset type to check that we don't include any any bad types here that we that we don't want here but here we don't have any such any such check we i can can write here anything that doesn't make sense and I don't have an error here.

Final Remarks and Q&A

Short description:

We have a bonus task related to type level operations. Contact Jirka on GitHub or join our Discord server for further assistance. Jirka usually Googles for answers and experiments to find solutions. There will be a recording available, and we'll provide a follow-up email with more information. If attending React Summit in New York, visit our booth. The recording and GitHub repository link will be shared.

Yeah. And and another method. And now if I write something that doesn't make sense, sense that he will tell me. Right here. So that's the that's where the satisfies keyword might come in useful. Okay. And this is this is all the tasks I prepared all the tasks. I've got a bonus task to where we cannot do a type level type operations from number like addition and multiplication but i'm not sure what's i think you're we are a bit late to do that what do you think on there now we can we can add it to the github repository so if anybody's interested in doing the extra thing or learning more they can always see the code so if in the repository there will be three branches with the solution of the bonus task as well. I think that'd be nice. Yeah, definitely upload that to the repository.

Okay, yeah. And I think I was just saying that if anybody has any questions, this is the unique opportunity to ask Jirka something, but he's a TypeScript guru at our company. So feel free to ask. Otherwise, Jirka, do you have anything else in the presentation, maybe a way to get in touch with you if anybody's interested.

Sure, you can contact me on GitHub. I don't know if that's possible. I didn't include my Twitter. Yes, you can contact me somewhere. I'm not sure. And there's also, we also as a company have a Discord server. So if you go to our website, maybe you can show it on the website where people can find the link. If you go to our content.ai main page, there's a link to Discord always in the footer. So, if you join the Discord server, Irkan, other DevRel guys are always there looking at the messages. So, you can get in touch via that platform as well.

There is one question about a TypeScript group or channel. Do you know if there is any place where people can get help? I know there is a Next.js Discord server. But whenever I tried to get some help there, I was very unlucky because the questions are very quickly disappearing. So what is your experience? Where are you, Jirko, finding answers to TypeScript problems? I usually just Google them. And, well, if I am unable to find an answer with Google, I just play around with stuff until I find the answer using, like, experimentation. I didn't contact any communities so far. I tried to use chat GPT. It sometimes can give you a usable answer. It's not 100% correct always. So if you use chat GPT, just double check the answer he gave you. But it can be useful to give you some leaps in your search. Perfect.

So just one last information for all the attendees. There will be recording available. There will be a follow-up email where we will give you all the chance to get in touch with us as well. So, if we have... or if the GoodNation team has your details, we will send you an email with all the information. So, when Erika prepares it, it will be available to you. Otherwise, if you're attending React Summit next week in New York... Stop by our booth. I will be... just come back... Okay, I think my main red collection is that again so you're if you can wrap it up. And yeah. So, so what Andre wanted to say is that if you if you are visiting the summit in the in the in New York, you can visit our booth and I will not be there but on the event been it will be there in person so you can talk with him, get a beer something. and, I don't know, that's probably all. And the recording will be available, don't forget about the recording. And we'll also provide the link, and you can visit the GitHub repo. I'll upload the final version there together with the title edition so you can see that in action. Thank you everyone, bye.