Advanced TypeScript types for fun and reliability

Rate this content

If you're looking to get the most out of TypeScript, this workshop is for you! In this interactive workshop, we will explore the use of advanced types to improve the safety and predictability of your TypeScript code. You will learn when to use types like unknown or never. We will explore the use of type predicates, guards and exhaustive checking to make your TypeScript code more reliable both at compile and run-time. You will learn about the built-in mapped types as well as how to create your own new type map utilities. And we will start programming in the TypeScript type system using conditional types and type inferring.

Are you familiar with the basics of TypeScript and want to dive deeper? Then please join me with your laptop in this advanced and interactive workshop to learn all these topics and more.

You can find the slides, with links, here:
And the repository we will be using is here:

116 min
24 May, 2022


Sign in or register to post your comment.

AI Generated Video Summary

This workshop covers advanced TypeScript types and features such as strict mode, type validation, error handling, and type mappings. It explores techniques to improve code reliability and prevent errors. Topics include treating elements as never being null, deriving TypeScript type definitions from schemas, using read-only types to enforce immutability, and utilizing type mappings like omit and pick. The workshop also emphasizes the importance of type safety, type readability, and exhaustiveness testing. Overall, it provides practical insights to enhance the reliability and type safety of TypeScript code.

1. Introduction to Workshop and Goals

Short description:

Good afternoon and welcome to this workshop, Advanced TypeScript Types for Fun & Reliability. The goal is to show you all sorts of interesting TypeScript things, including setting strict on, validating data at compile time and runtime, creating custom types based on other types, using unknown, never, and any types, working with opaque types, type predicates, type assertions, and completeness checking. The workshop will be interactive, with opportunities to write code and learn from errors. Prerequisites include Node and npm, with specific versions recommended. Links to slides and the repository will be provided for reference.

So good afternoon, everyone. At least for me it's afternoon. This being online, I'm not quite sure if it's afternoon for all of you, but here in the Netherlands it's afternoon.

So good afternoon and welcome to this workshop, Advanced TypeScript Types for Fun & Reliability. So my name is Maurice de Beyer, also known as the problem solver. I do a lot of different things, but I develop lots of TypeScript stuff. I'm an instructor, I do workshops, I'm a Microsoft MVP, although that isn't all that interesting here. That said, TypeScript does come from Microsoft. If you scan the QR code or you take a look at my website, you can see more about me, but there isn't actually all that much interesting there. So it's not about me, it's about what we're going to do in this workshop.

So what's the goal of this workshop? Basically, I want to show you all sorts of interesting TypeScript things, which you might know, might not know. I'm sure I'm going to do some stuff you'll know. The first one's actually not that advanced, so I'm pretty sure that everyone will know about that, which is actually about setting strict on. But then I'll continue with some more strict setting, which most people are not aware of. I'm going to take a look at how you can validate data, not just at compile time, but also runtime. And combine those two validations. We're going to take a look at creating custom types based on other types, what I call map types. How to create your own custom type mappings, we'll take a look at when to use unknown and the never type and the any type as well. Although that's not all that interesting, the any type. It will still come up. I'm going to take a look at opaque types, which is a way where you can make type scripts even better informed about different types. Because in lots of cases, things boil down to two basic types like string number, et cetera. And the first name and the last name are both a string, but in some cases, you might actually want to... well, differentiate between the two. We'll look at type predicates, type assertions. I've got some stuff about completeness checking, and I've got random other things as well. Plenty of stuff. In fact, I think I've got more stuff than we can actually handle, so we're probably not going to get to all of it.

Now about the workshop, we're going to make this pretty interactive. It's not just a big presentation. I'm really hoping you'll write lots of code yourself. I'll provide links to all of the codes. In some cases, it's easier to copy something than try to type everything, but in general, I recommend you type everything in. You will make errors, but that's good because in real life, when you code an application, you'll make errors as well. If you see the same kind of error messages, it's like, I've seen that before. I know how to fix it instead of when you see it the first time. Because now you can copy something and you can see the original, but if you do build an application for real, it's typically not a matter of copy and paste because no one already wrote it for you to copy it. Got a couple of prerequisites. We're gonna install a starter application for a moment, which we're gonna work with. But of course, we need a Node runtime. So we need Node. We need npm to get started with. Basically, what you can do is go to a terminal window like this, and let me zoom in a bit. You can do Node dash dash version or dash v. And I've actually got a relatively old version of Node, but if you've got anything like version Node 12.2 or later, you're good to go. It doesn't need to be the latest version. Also need npm to install npm packages. So we can do the same with npm, npm dash dash version. And I'm on version 7.24.0, which is also not exactly the latest, but any version of npm version seven or even six, for that matter is going to be fine. The reason that we need Node 12.2 is not because TypeScript depends on it, TypeScript doesn't, but I've also actually used Vite as a little, well, development tool for the workshop. And Vite has the dependency on Node 12.2. React itself is actually pretty relaxed. I'm not sure what the minimum version of react is, but it will work on pretty much every version of Node which is out there.

Now, I've got two links here. I've got a copy of the slides online, and of course that opens up on my other window. So let's slide it over. So all the slides I've got here, I'm going to use are here. Having these available is kind of nice. Like if I look up a code slide like this, if I command click on that, you will actually get to the change to commit in GitHub, which contains the actual code there. So sometimes it's just like, okay, what's exactly the change? Well, over here, you can see a change being made. So it's kind of useful to keep that around. The other thing was the repository. We're going to use that as the basis, and if you're in the slides, of course you can find the repository because there is link there. If you go to the repository first, you can link to the slides right here. So either way, you can find them. Now we need to clone this. So what I'm going to do is I'm just going to copy this clone link and it doesn't matter whether you use HTTPS or SSH, I'll just default to SSH. And we'll go back to the terminal and we need to do a git clone of that repository. Once we've done that, let me actually go through the slides because there is a bit more to see here. Here's a list of all the changes which you can find in the repository.

2. Setting Up and Introduction

Short description:

You'll see Jean Luc Picard often, indicating it's time to do something. We'll use breakout rooms to split into groups, clone the Git repository, install npm packages, and start the application. The code is not perfect and uses unsafe HTML building. Use React, Vue, or Svelte for production. The application has various features, including adding pizzas and checking out. Ensure prerequisites are set up. Clone the repository and do npm install. We'll switch to a broken branch and fix it with TypeScript. Every step is committed, allowing you to start from any point. If you encounter an error with worker threats, update your Node version to at least 12.2.

You'll also see this guy, Jean Luc Picard. He will come by quite often, and that's typically your cue, OK, now it's time to do something. I will tell you as well. So I'll make it clear. I'm also going to use the breakout rooms feature from Zoom. So you'll notice I'll split you up into breakout rooms, like, what is it, three or four people in a room? I'll open that many rooms. I'll open them for a few minutes, which is enough to do whatever the task at hand is. And then it will close down. You'll get a little notification saying it's about to close down, but then it will close down and we'll all come back to the main area. Now, if you're in a breakout room and you get stuck, you can always ask me to join you in that breakout room to explain something. Of course, you can chat with each other as well, because that's the whole point of using breakout rooms.

So we need to clone that Git repository, which I just did. And I see someone else already pasted a copy of the URL to the repository in the chat window. I also did in the Discord window. So it's available in both the Zoom and the Discord chat window. Once we've cloned the repository, we need to install the npm packages. So let me go back to the terminal, cd.npm, cd into that TS advanced folder, do an npm install. It should be relatively fast. It depends a bit on your internet speed and your machine, of course. But for me, it installs in like two seconds. I'll be using Visual Studio Code a lot. So I'm going to start up code. And then with npm, sorry, not npm start, but npm run dev, we can start the application in the development environment. As I mentioned, I'm using Vite here, which is a really nice development environment, which compiles TypeScript really fast, reloads the browser really fast. So if I click on this link, my application shoot up, open up. There it is. It's a little pizza shop where we can add pizzas. Like I want pepperoni, and I want a pepper pizza with some extra cheese, and then I can check it out, and it basically does something like that. This version actually works relatively well. We're going to switch to another branch in a moment, where it's sort of broken, and it leaves a couple of things to be desired. The code by itself is actually far from perfect. Let me show you some codes, because this is focused on TypeScripts. And if I scroll down a bit, for instance, you can see here, I'm setting the inner HTML of an element, embedding expressions in there. This is not a very safe way to build an application. I wanted to focus on TypeScript. I did not want to embed React or Vue or Svelte or any of these things in here. I want to focus on the TypeScript part, but I still wanted something visible, so I wanted to keep things as simple as possible, so I did it this way. But keep in mind, I'm very much not recommending you write a production application building your HTML this way. Use React, use Vue, use Angular, Svelte, whatever. Do it in a safe way. This opens up a whole host of possibilities for injection, attacks, and things like that, which you don't want to do. That's a little caveat about this application. Let's go back to the slides. We saw start the application with npm. Run dev. We saw the application, localhost port 3000. With the pizzas there, you can add pizzas like I just did. You can press the Checkout button where you'll get a little pop-up showing you what you ordered. It's time to make sure that these prerequisites are set up. Someone was asking about the get link in the chat window so let me copy that again to make sure everyone has that. That's in GitHub and I'll copy the slides as well once more. That's a link to the slides. Let me create breakout rooms. I need 11, I think. No? 10? Yes, that's 3-4 people. And let me check the options. It's set for four minutes which should be enough and you'll get a little countdown timer once a minute before the end. So after three minutes you'll get a little countdown timer. So I'm going to open up the rooms. Please do this step where you clone the repository, do the npm install and make sure the application runs and behaves like I just did. And then in the next step, we're actually going to switch to another branch where the application is broken and then we're going to start fixing it with the help of TypeScript.

The first thing I wanted to do was enable strict but before I do, I actually wanted to show something else. I've got the application here and if I go to the source control tree, there's a whole bunch of commits here. We're going to switch to a different branch in a minute, but basically every step we're going to do is commit here. So they all built on top of each other. So if you miss a certain step, you'll have a slight problem in the next step, which you can always do in Visual Studio Code if that happens kind of go here and say, create a branch from this step, or then continue with that. So if you've got any of the stuff in front missing then you can get started from that. And then the chat winning, I'm seeing, cannot find module worker threats error from Avinaj. I would check whether your version of Node is up to date. I have seen errors like that before, and that has to do with older versions of Node being installed because feet, which we're using here, actually depends on at least Node 12.2.

3. Switching to a Different Branch

Short description:

Try Node dash dash version and see if that's a recent version. We're going to start by switching to a different branch called origin slash zero zero start, which has several bugs that we'll fix.

So try Node dash dash version and see if that's a recent version. Now, we're going to start by switching to a different branch. So right here at the bottom, you can see different branches. For those who kind of missed what I did, that zooms in on the wrong window. Right here, you've got main, which is a little button, although it doesn't look like one, but it is a little button in Visual Studio Code. And if you click on that, it will let you select another branch. I click on that and then at the top, you see here a little pop-up with the available branches. You'll see this one, origin slash zero zero start, which is the branch we're going to switch to and start fixing things. Because on that branch, there are several bugs. So let me do that again and actually switch. So now I've switched to another branch, and right here, not right there, right here now it should say zero zero start.

4. Enabling Strict Mode and Handling Compile Errors

Short description:

In this section, we enable strict mode in TypeScript and explore the resulting compile errors. We examine a specific error related to a variable being of type 'pizza' or 'undefined' and propose a solution to fix it. We also discuss inlay hints and how they provide useful information in the code editor. Additionally, we address the issue of a program bug and suggest a better approach by throwing a new error. Lastly, we touch on the issue of a variable possibly being null and explain the reason behind it.

So the first thing we're going to do is we're going to enable strict mode. And strict mode is relatively well known given that this is an advanced TypeScript workshop. I'm going to assume that everyone is familiar with it, but it's not the default with TypeScript. Like, I've got a pretty standard TS config file here. Let me open that up.

So over here, I've got a TS config file. There's some standard settings. No emit is true, for instance, which is a V thing because V bundles everything up. Source maps are created, things like that, but there is no strict setting here. And it turns out that when you enable strict mode, TypeScript does type checking quite a bit stricter. And right now, if I now open up main.ts, then there are no errors. Then there are no errors, there are no read squigglies anywhere. Everything is happy. And for instance, here, if I look at this main element, it actually says so here because of the inlay hints. It tells me it's typed as an HTML element. And what was that main? Somewhere down here, like main.appendchild. It's perfectly fine. No compile errors. But if we go here and I'll turn strict on and that defaults to false, so we want to turn it to true. And now I go back to that same main.ts. We can see over here the PC would win a bit, we can see quite a few compile errors appear. TypeScript is all of a sudden no longer happy with our code. I'm guessing most of you have seen this. Most of you are familiar with strict mode. So let's take a look at some of those. Right here, for instance, there is an error where it says pizza. The variable pizza is of type pizza or undefined and exactly the same here. Where does that come from? Pizza is defined over here. And it's a result of a WeakMap where we get something based on an HTML form. And it turns out the code is based on... That was actually the one I was looking for, the code is like this. There are lots of HTML forms being added and they are all added to that form to pizza map, WeakMap object, so every time we use something from there, it should really be in there. There are several ways we can fix this, but one very good way is just to check whether pizza is actually... Not that one, this one. Whether pizza is actually an object. Because if I check here and say, if pizza is an object, or not an object, and for instance, I do a return here. Then this compile error should go away. That should be if not pizza, of course. So now compile error goes away here. This kind of solves my compiled problem, but what happens if that pizza really isn't an object? It means I've got a bug in my application somewhere. And this return kind of means, well, we're just skipping over the code, which would result in errors not doing anything. So not exactly ideal. So question from John, what am I using for in-lab type? Hence, I'm actually not using a plug-in at all. If you go to your settings and I just hit Control-J, or you can go to file preferences, settings. That's the same thing. If you search in here, you'll find inlay hints. It helps if I type in the search box. You'll see these things, inlay hints. And I've got them enabled. And if you look underneath the Typescript part, which is also always available, you'll find lots of options here. And they're basically duplicated. They show up for JavaScript here, and below we've got the same list for TypeScript. I've basically enabled most of those, which means that if I'm in code, it tells me. I don't have to hover over form to see that it's typed as an HTML form element. It will just tell me in line. And here it tells me pizza is now pizza or undefined. And down here, I still have to check with hovering, it tells me it's a pizza. It's maybe a little in your face, but it does provide a lot of additional useful information. But sometimes it's too much in my face. I do turn it off. But I kept it on for now. But I was saying this return isn't perfect because it's like there is a program bug here. So what's better is to throw a new error, no pizza found. And in that case, it's still perfectly happy because TypeScript knows by the time we get here, this pizza object will always be a pizza object. If it was undefined, it was false. It will throw an error here. Other ways we can fix things, like over here it says, well this, actually, let me do the first one. Over here it says that total price elements is possibly no. Before it was typed as an HTML element, now it says, well, it's an HTML element or null, which makes sense, because over here, I'm doing a document.getElementById with an id of order of total. TypeScript doesn't know whether that exists.

5. Treating Elements as Never Being Null

Short description:

getElementById returns null if the element doesn't exist. TypeScript can be used to treat an element as never being null by using an exclamation mark. However, using a question mark may lead to potential problems. For example, if the element is supposed to be there but isn't, the application will be broken without a runtime error. It's better to use an exclamation mark to ensure that the element is always treated as an object. The changes made include setting strict to true and throwing an error in case the pizza element is not found.

It might exist. It might not exist. If it doesn't exist, getElementById returns null. If it does exist, it returns the related DOM element. With strict turn off, it will just say, well, it returns an HTML element, and we're all good. But with strict turn off, TypeScript actually tells us about that possibility.

Now, in this case, I've got my HTML here, and that element is in here. What was its order total? So, somewhere in here, there it is, is an HTML element with order total. TypeScript doesn't check this, so it doesn't know, but I really think this is part of the code. So, I can say, well, you think this might be null. I know this will never be null at runtime, because that is part of the source code. So, I can put an exclamation mark there, saying, okay, treat this as never being null. It's always an object, and then it's perfectly happy. And I can do that in an expression like this.

Over here, with that order, exactly the same thing happens. So, I could put that exclamation mark there, but it's actually used down here as well. So, I could do it twice. But another way I could do it, say, put the exclamation mark there. Now, we can see from the inlay hunt that the order element changes from HTML element or null to just an HTML element. Because, again, this is in part of my source code. It's HTML, but it's still sourced. It's still source code which I publish, so I know it should be there. Of course, if it's not there at runtime, we'll get a runtime exception. But that's correct, because there is a bug in my code. There is the same here, main, and that actually, again, is typed as an HTML element or null. I could do the same, an exclamation mark, which I think is the correct thing, because, again, it's part of the source code, shouldn't be there. But actually, I want to show you a different way of doing it and also a potential problem with that, because instead of an exclamation mark where I say, well, main is not null, it's always going to be an HTML element, so it's perfectly safe to call this, with a question mark, I'm actually telling the TypeScript compiler it may be an HTML element, it may be null, but just call this append-child if main is an element and ignore it if it's not. Now, that sounds very much like the same thing, but let's see what the actual selector was for main. Where was that defined? Somewhere a little higher. There, so that's a menu element. Let me search for a menu elements. Here it is. So let's change this for a moment and call it de-menu and go back to the application, if I can find it. There it is. So all of a sudden, that menu of pizzas is gone. If I open up the development tools, no error, nothing. Basically TypeScript now says, okay we'll check whether we found something over here. If not, we'll just skip that append child, we'll not do it. Which means that the menu of pizzas is never built up. And that kind of means that our application is broken, but we don't get a runtime error. So in a case like this, I think putting an exclamation mark there saying this should always be an object is much better. Now, if I go back to the application, it's still broken, but at least we get a clear error. And assuming you've got something like Sentry set up for error logging, we can see here that's the application was broken because we're calling append child on something which is supposed to be there, but isn't. So let me actually fix this again. So that should just be menu instead of dmenu. And we've got a working application again.

The changes I made, I basically set strict to true. And I threw an error in case of not being able to find the pizza with the relative form, using the exclamation mark to tell TypeScript, okay, this item is always going to be an object. It's not going to be null, preventing some errors that way. So please add this. I'm going to open up the breakout rooms for four minutes again. It's a pretty small change, few simple compile errors to fix. So please go and do this, and I'll see you all back here in a few minutes for next step.

6. Exploring Strict Features in TypeScript

Short description:

There are more strict features in TypeScript beyond UStrict. You can make TypeScript even stricter by changing the defaults for specific settings. One interesting setting is no unchecked index access, which helps avoid potential bugs. There are also settings for unused variables and unreachable code. By enabling these settings and fixing the resulting errors, the code becomes more reliable. GitHub Copilot is a convenient tool for generating code snippets. Some settings produce warnings instead of errors. By commenting out specific settings in the tsconfig file, the corresponding warnings can be removed.

So I'm guessing most of you were familiar with UStrict. It's a pretty neat option, but it turns out that there are more strict features. And a lot of people are not aware of that. And it turns out there are actually quite a few. I've got a list of settings here which will make TypeScript stricter, which are not affected by Strict itself, because that's what I turned on. And Strict turns a whole bunch of settings on. You can control all of those individually. But if you want TypeScript's even stricter, then you need to change these defaults as well.

Now there are a couple of specifically interesting ones. Like no unchecked index axis is useful. If you check in Array, for instance, if a certain element is there, well, it might be there and by default TypeScript will type it as whatever that array is typed. But if you're querying something which is actually outside of the bounds of the array, like there are five items there and you're checking for the 10th item, you'll get undefined back. And right now, TypeScript doesn't really warn us about that and there are potential bugs there. Others are unused variables, unreachable code, these kind of things. So, let's turn those on and see what comes up.

Now, there's quite a list here, so I'm actually going to copy that. If I can get the code to show up, there it is. I'm just going to copy this list and put it in the tsconfig. Those were doubles, so now I should have exactly the same list here. The ones I'm actually using are the no-unchecked index, the no-unused parameters, and the no-unused locals. That's the ones I'll be using right now. But there are additional things which are very interesting like no-import, no-unchecked. Which are very interesting, like no-implicit returns where you might forget a return in one branch or something like that, or overrides, where you might overwrite things when you're defining subclasses, and unintentionally overwrite a class, have code which is unreachable, things like that. So lots of useful checks here. Now if I go back to main.ts with this, we can see right here a bunch of red squigglies again, a couple of errors. So let's go and take a look at those. One new one is here, it says, well, that extra ingredient which we can select is an extra ingredient or undefined, so we can't dereference price from it. Which in the application, where did it go? There is these ingredients, like I want some extra cheese, extra pepperoni on it and those are added to the order and included in the price. Well, the reason this could be undefined is not because extra ingredients is an array, if I look at the typings here, it's of type extra ingredients which is an object and the definition of this is it's got basically a key which can be any string and in there it has an extra ingredient type which has a name and a price. Which means that down here where we're de-referencing it, we're putting in a name which is some string. Well, maybe it's in there, maybe it isn't. So in order to keep TypeScript happy, we have to either provide a default here, we could do that. I could do something like to question mark dot and then provide the default with the coalescence operator something like this. And that actually still complains. I think that should work, but there is actually another error down here. So this is not the best way to do it. So let's get rid of this. What's better in this case is if this return's undefined, then return default here. So I'm going to use the null coalescence operator here. And basically I'm going to say, well, I'm going to return another extra ingredient definition with that same name. I don't know what the price is, so I'm going to make the price zero euros. We're all happy. So now when we get to this line, we know that extra ingredient is always there. It's either the one we found or the default. The same happens here with that same extra toppings. Again, we're looking for that extra topping right here. It might be there. It might not be. So use the null coalescence operator to put that in. And GitHub Copilot is really convenient. It knows what I'm going to do, because I've done it before. So it knows I want this object in there. Saves a lot of typing in examples like that. So these errors were a result of me adding, where's the tisconfig? Adding that no unchecked index access, which is highly recommended to always enable. But remember that's not enabled by Strict. It's a separate thing. Now we've also got some unused variables in here, but there are no more red squiggles here, like apparently we don't have any more errors. Some of these settings produce warnings not errors. So I could go here. Here, let's see, I can find the right thing. Yeah, right here. Let me put an empty line here. Here you can see that there is a warning now about that recalculates parameter type Boolean, which is never used. If I go back to the tsconfig and I say... We'll comment this one out. I go back. Now that error will disappear. It takes a second for it to realize it, but it should disappear. Do I need to do that one as well? Interesting. I was expecting this error to disappear.

7. Fixing Errors and Moving Forward

Short description:

Let's fix the issue of disabling the wrong things. After making the necessary changes, there are no more errors or warnings, and the application is functioning correctly. Let's proceed to the next step.

It wasn't there before. It wasn't there before. It shouldn't be there now as I'm disabling the wrong things. Let's fix this. It's not needed. We'll get rid of this. Of course, it is being used in a call here, so don't need this parameter there, which means that there is another warning which appears now, which is for this constant, which we don't need. And with those changes made, there are no more errors or warnings here, and everything is green and we're all good to go. And the application should still work just fine, so let's check. You can add something. We can. Actually, I wanted to add it again. We can pay for it, although there is something really weird here, but we'll come back to that later. So the changes to the ts-config, the changes with the default options here I made to the codes to figure out defaults if objects or array elements don't exist, in this case it was all objects. The use parameter and please go and make this same addition. Let's continue with the next step.

8. Validating Data at the Boundary

Short description:

In this step, we validate data at the boundary, which involves checking inputs from users and fetching data from the backend. We encounter an issue where the order total is incorrect, but TypeScript does not identify the problem. To address this, we introduce the Zot tool, which allows us to validate data. We create a schema for the pizzas and apply it to the pizza data in the main application. This ensures that the data matches our expectations and eliminates potential errors.

So the next step, we're going to validate the data at the boundary. So what does validating data at the boundary mean? Well lots of people validate inputs from users. Like you might ask a user for the address, then you'll check that the street is not empty, that our house number is filled in, zip code, city, that kind of thing. But there is also another boundary and that's one at the backend. You will be fetching data from the backend and you might have a lot of TypeScript setup, and there are TypeScript types describing all of that data, but does that actually match your expectation? And let's take a quick look at the application. We've got the application here and let me refresh so there's nothing in the order, we've got a whole bunch of pizzas, and if I take the bottom one, the pizza with spinach, we see that our order total is €12.50. All good and well, but then I add another one, the pepperoni pizza, for instance, and all of a sudden we see €12.50 plus €12.50 isn't €25, but it's nan, not the number. So apparently there is something wrong with my application, but TypeScript doesn't complain. Like if I check main here there are no compile errors, nothing. If I check the console, there are no errors there. So something is wrong, but no one is telling me what. Now I can spend a lot of time debugging and I actually know what's the matter. So I could find it relatively easy if you know what it is because you intentionally make that error. But if you have this in a production application, it can be pretty hard because in this case it actually depends on the data. It's let me do that again. It's not just whether you add two pizzas. I can add this pizza, this pizza, and I get a total. It's only with the very last one. If I add that, then I'll order total is mocked up. But over here, it actually looks fine. The pizza, which spinach is 12.50, the other side of the price. Turns out that there is a problem in my data but TypeScript doesn't know, the run time doesn't tell me, nothing tells me where to look. So I could go hunt through that data and I could probably find it. I might go into Network and see well, there is a bunch of data being loaded here. Something wrong here, something wrong here, maybe, maybe not, kind of hard to tell, especially if that's a large data set. Now, there is a tool I use a lot. And that's called Zot. And Zot, here I added to dependency, will let you validate data. And there are quite a few similar tools like that. Typically, they're used to validate data from the end user. Like the end user does something, maybe enter their credit card. We validate whether it's a proper credit card. But it's also very useful for these kinds of things. So let's go to the application. We'll create a new file, like this. Schemas.ts. I'll import Zot in here. Import Zot from Zot. And I'll define a schema for those pizzas. Now, I already have a type for those pizzas and that looks like this. So let's copy these properties for a moment and we'll define a schema. So const pizza schema is Zot.object. Now it doesn't have any properties yet, but let's add those. And in order to tell Zot that this is a string, I can say this is Z.string. And I can add other things to that. Like it's mandatory or what is it? Should not be empty, for instance. And the same with a number. I can say the price should be Zot.number. And for instance, it should not be a negative or an empty price. It should be a positive number. And I need to add commas there. So the image URL is, again, Zot.string. And I could specify this is a URL, like this, but in this case, it's a relative URL, so it doesn't actually recognize it as such. So I'm just going to say it's another string, but it should not be empty. The same here with the image credits. The extras are an array, so Zot.array of a Zot.string. That could potentially be empty, so we'll leave that out. We'll export this. And now I'll take this pizza schema and I'll go to the main application. And I've got a function here, loadPizzas, which returns the actual pizza data. And instead of just returning, I'm gonna use that schema, import it and then called the parse function on that. Go and parse that data through that schema. So the schema will return it. And actually, that's the wrong one because this returns an array of pizzas. So I should have a pizza array. Export. Pizza array. Schema is a Zot.array of the pizza schema, and that's the one I should be using. Let's resolve this one, so it's imported, and now it's happy. No compile errors.

9. Fixing Typing Errors and Validating Data

Short description:

After fixing the typing error and removing quotes from the price, the menu is no longer empty and the expected prices are displayed. The extra ingredients are also loaded, and a schema is created to ensure the name and price properties are present and valid. The application catches errors when required properties are missing or have incorrect types. The Zot tool is introduced for data validation, and schemas are added for the pizza and extra ingredients. The load functions are updated to validate the data using the parse function. This approach allows for checking the API data against expected types.

It did compile before, or it did have a compile error before, because it actually realized that that pizza array is a different shape than the schema returned. So it knew that there was a typing error. Now the types are correct, and this will actually check that data being returned. Now if I go back to the application, all of a sudden, our menu is empty. I can see down here there is an error, and if I go to the console, I can see there is some, I want to zoom in, but it zooms in on the wrong screen. That's the right one. So it's zoomed. It produced an error here, and it says, well, there was an invalid type. We expected the number, but we actually received a string. Where was it? The path, it points to whatever was offending. So it says it's an array with the fourth element, and then the property was price, and then again with error messages, expect the number retrieved string. So let's go and take a look at the data. So in API, it was this pizza JSON, which is loaded. So on element four, it's error base, so that's the fifth one, that's this one, and right here, you can immediately see like price is 10, 35, but here it's the string 12.5. So remove the quotes. Now it's a number. Go back to the application, go back to the application refresh. No errors, and we have our menu again. And now if I add pizza with spinach and a pepperoni pizza, I do get the expected 25 euros, which is really nice. But there's more data we're loading. There's also this extra ingredients which we're loading so let's do the same for that one. So we'll go here, we'll create another schema, not pizza array schema, but ingredient schema is an object and it has two properties, a name which should be there and a price which should be positive again. And then there is the extra ingredients object which contains this. So I should have copied a bit more. Extra ingredients and we can create that one, couple of different ways. It's not an array, it's what we call a record. So it has a name as a key and extra ingredients. And I've actually got a typo here, that's the data. So we'll post that in. And then that's happy. So let's add this to the main.ts. So here I'm loading those extra ingredients. We'll do that same trick here,.parse. If I can type parse. I still have to resolve the schema, import the schema. Now there weren't any errors in there. So my code just runs fine but let's quickly introduce an error there. Say this one doesn't have a name. We'll call it X name, for instance. And I refresh the application. Again, we get a runtime error We get a runtime error saying that in the mushroom objects, the name property was missing. It was required and we expected it to be a string but it was actually undefined. So it catches all sorts of errors like that. Now let's fix this again and make sure that my pizza and extra ingredients data actually load and it works just fine. So just to make sure, let's add to pizzas and it sums them up just fine again. So where are my slides there. So I added Zot. I didn't install it. If you did the NPM install on the main branch then Zot was already in there. So you can start using it right away. I added those schemas. I created schema.ts and added schemas for the pizza that pizza array, extra ingredients and the extra ingredients like that. And then I updated those two load functions to run that parse function to validate the data. Pretty nice, pretty neat. Works really well. And that way you can kind of check whether the data from the API is what you expect it to be. I wanted to get John Luekpicard again. So the next step, please go and implement this. Now, before we do, I did have a bit, a bit of co-duplication with the types. I've got a schema describing the type and I've got a typescript type, which typescript actually uses, which is not so nice. You might've noticed that. In the next step, we're going to address that, and we're going to make those a single one. So a single definition of the piece of type and the extraingredient type. But that's the next step. So let's do the next step. And that's where I want to get rid of the duplicate definitions. And just to make sure that everyone is at the same place. Like here, I've got a definition of what the pizza looks like for Zot. So I'm saying there is a name, which is a string on empty. And there is a price, et cetera, image URL credits, stuff like that.

10. Deriving TypeScript Type Definitions from Schemas

Short description:

Zot has a neat feature where you can use Zot.infer to derive a TypeScript type definition from a schema. This allows us to have a single source of truth for our type definition. We can get rid of unnecessary duplication and ensure that our types are consistent. YUP also has a similar feature, but Zot is recommended as it was written in TypeScript from the beginning and works slightly better. While YUP has made improvements and is now natively TypeScript, there may still be some outstanding issues with inferring static types from schemas.

And, where is it? Types.ts. I've got this similar definition. It's not the same, but it's a TypeScript interface where I'm saying, well, there's a name, a price. And I can't say it's not empty, but I am saying it's a string, a number, things like that. So there's a lot of duplication between the two. And it's quite possible that you might come in and say, well, this should have, well, say, credits. For instance, some kind of credits for who came up with the idea of this pizza. Well, it's in the type now, but it's not here. Stuff like that. So let's remove that credit. And let's see how we can actually fix that. Well, it turns out Zot has a really neat feature. And several other libraries like Yupp have this too. But as far as I know, IOTS, which came up a minute ago, doesn't have that same capability. And with Zot what you can do is, here, you can use Zot.infer. And you pass in schema, or the type of schema, and that should actually be angled brackets. It will give you a TypeScript type definition which matches that schema. So we can kind of just write our Zot schemas and derived the TypeScript definition from that schema. So let's go and do that. So instead of having this pizza interface, I could say, well, we want to export pizza. Now, we can't have an interface pizza because if you derive a type from another type, it's always going to be a type alias. So need to use the type keyboard. It should still be named pizza without the curly braces. And this is, and then I need to import Zot first. Import Zot, and then we say zot.infer. And not like in the slides, but with angle brackets, you specify the type of the schema you want to infer, like this. And if I import that, it actually knows what it is. And then if I look at the definition, it tells me, well, the type pizza is, has a name, a price, an image URL, credit extras. It doesn't know anything about a positive number for price because that's not a TypeScript type. But if I go back here and say, well, it should also have that credits. Set zot string. I go back here and look at my pizza type, and you see down here, credits show up. So we've got a single source of truth for our type definition. I actually want to get rid of that credits because that will throw an error because it doesn't exist in the data. But we've got a single source for truth, just this schema, and none of the other stuff. So we can say, well, pizza is like that. Pizza array is still fine. Extra ingredients. we've got those. That's going to be a type is Z.INFER of type of extra ingredients schema. This is singular. Get rid of those. Actually, that was not singular but plural. So that's the one that should be here with an equals. So that's the record. And I need to change that from an interface to a type alias. And now all the typing should be the same. If I look at the extra ingredients, I can see that's an object with a name and a price. The extra ingredients is a dictionary which has a key string and an extra ingredient with the name and price. The pizza array is still an array of pizzas. A pizza is still that object with the name, price, image, URL, credit, and the extra options you can order for that pizza, but all with a single source of truth. So, much nicer. Get rid of this, and we're all good to go. I could do also do that for item orders, but we're actually gonna do something different with that. It would be perfectly fine to create a Zot Schema for that and infer it in the same way, but I've got slightly different plans for that. So we're not gonna do that. So, Brian just pointed out that YUP has some outstanding issues with inferring static types from Schemas. Not sure about that specific issue. We can actually take a quick look. Assert of a YUP type seems not working. Turns out that's a pretty old bug. Not sure if that's still relevant. Because I happened to have done this infer implementation for YUP initially. Because initially, YUP was written in Javascript, so it didn't know anything about inferring types, it didn't know anything about TypeScript. And there was a type definition on DefinitelyTyped, and I was one of the contributors to it, so I added that infer capability to it. But, because YUP was not written in TypeScript, it did some things which were not completely TypeScript-compatible, and as a result, it was never 100%. It worked relatively well, but it wasn't perfect. That's why I actually recommend people to use Zot, instead of YUP. Zot was written in TypeScript from the get-go with this as one of the goals, so that works slightly better. These days, YUP has been rewritten in TypeScript, so it is natively TypeScript now, and the infer logic has been rewritten as well, so I'm guessing this bug, even though it's open, is actually fixed, although it doesn't say here.

11. Exploring Type Aliases and Try-Catch Statements

Short description:

There are more options like IOTS for inferring mechanisms. Interface and type alias are almost identical, with a few differences such as extending. Type mappings like Z.Infer can be used to map from a schema type to a regular TypeScript type. Try-catch statements are used to handle errors, and the error type can be any or unknown depending on the strict setting. Printing the error message requires a type check to ensure it is an instance of the error class.

There's still things from this year. And apparently, Chuck mentions that IOTS does provide this same inferring mechanism. I wasn't aware of that. I thought IOTS didn't, but in that case, that's another great option to look at. There are more options like this. Let's go back to the slides.

So a new type definitions, so remember we need to do a type alias now, so I might have a few things to say about interface versus type alias. In general, when I write TypeScript codes, if I just define it, I'll use an interface. If I start using additional features like type mapping, then you are forced to use a type alias like this. Initially, I prefer an interface, but it's a pretty, well, weak preference. Like type or interface are almost identical. There are very few things you can do with an interface you can't do with the type or the other way around. So it's really a matter of preference. One of the very few differences which you can do with an interface you can't do with type is extending. If you've got two interfaces with the same name in the same scope and they have different definitions, their definitions will be merged by TypeScript. And that does not happen with a type alias. Of course I use lots of type mappings these days. We'll see several one but Z.Infer is a type mapping where you map from something that type of that schema to a regular TypeScript type. And in all of those cases, you need to use types anyway. If you want, you pretty much can use types everywhere. If you use inheritance, like, or implement, I should say a class implements an interface, well, it can implement that type alias just as well. If you extend an interface from another interface, you can extend it from a type as well. So almost the same capabilities there. It's just one way or another. Doesn't really matter that much. Let's continue with the next step, and we're gonna take a look at try-catch statements. And let's actually first go to the code. And I first want to revert one little thing in DsConfig. I want to set strict to false again, and then in my main, I'm gonna add a try-catch, like in it, I'm gonna add everything inside of a try-catch. And assuming there's some error, we might wanna write some console log or send the air to something like sentry or some other error collection error surface. But I'm just gonna do a console.log, oh, sorry, console.error, actually. And I might wanna do something like print just a message. And why is this? I've actually got a bracket too much. And actually, I thought with strict false, that would be type slightly different, but by default, if you check the error, it's gonna be of type any, not unknown. And I can actually do that explicitly. I was under the assumption that resetting strict would do that, but I guess it doesn't. It's another setting that also affects the type. It's another setting that also affects that. So, I will set that back to true. And this is kind of what you get by default, any. So, you can print a message like that and that would actually work. So, let's say I changed this URL so it's invalid. I go to the application. There it is. Refresh it. Down here, we'll see an error being printed. Actually, the error is this one. Printed, an extended end of JSON because we didn't get a JSON response. Now, if I remove this any, right now, error is gonna be of type unknown. And what always trips people up, they think, well, this really should be of type error. But the problem is that a catch is pretty much impossible to type properly. The reason it's impossible is that in JavaScript, you can throw anything. Like I could fix this URL, so that doesn't actually throw. So our code runs again, but now, in here, I could say throw the string, this is a test or an error or whatever. So TypeScript complains about this unreasonable code. But other than that, it's perfectly happy. And JavaScript would be perfectly happy. Because you can throw anything you want to. I could throw an object. I could throw an error, which is what makes most sense. But I could throw a string. I could throw undefined, if I wanted to. So that's why, by default, the error is of type any, and if you're in a structure mode, error is going to be unknown. So how can we print this message? Because there might be a message there. Well, we want to do a type check. So we want to do something like error. If that's an instance of the error object, then we want to print that error dot message. And now that compiles, because now it knows we only will get in here. If the error is actually an instance of an error class, that means there is a message. Now, if I comment this out for a moment and reintroduce the error with the URL, we can see.

12. Error Handling and Type Mappings

Short description:

In this section, we explore error handling in TypeScript and how to handle different types of errors. We discuss the use of unknown and any types, as well as the limitations of error handling in TypeScript. Additionally, we demonstrate how to format error messages using CSS styles in the console. Next, we move on to type mappings and specifically focus on read-only types. We explain the concept of read-only types and their usefulness in preventing property modifications. We also mention other map types available in TypeScript and their potential applications. Finally, we highlight a bug in the application related to adding toppings and prices, and explain how TypeScript can help identify and resolve such issues.

We can see that error message being printed there. But if you throw a string, I go back to the code. Where is it? Is my switching not working there? I refresh it. I need to undo this because that actually happens first. And now, no error message is printed to the console whatsoever. So if you do something like that, you also have to cater for the fact that error is not of type error at all, so we'll have to do an else. And we'll just console.log the whole error, whatever it is. So in this case, that's going to be that string. And now down here, we can see that whole error, the string or whatever was thrown. And we print that to the console. It's not very nice, but that's a JavaScript thing. In JavaScript, you can throw anything you want to, which means that in TypeScript, which is a super set of JavaScript, you can do exactly the same. And TypeScript kind of has to follow the JavaScript lead here, and say well, if anything can be thrown, then error can be either any, which is kind of, I don't know, but we'll just hope for the best, or unknown, which is much better, saying I don't know what it is, so I'll explicitly test for it. There are much better structures if you want to handle errors and use errors, like there is structure where you can return a maybe which is a left and a right property, and never both, always one of them. And if everything goes right, you'll get the right. If ever something goes wrong, you'll get the left property, and that typically contains error. But that won't even work here, because in a case like this, it is just gonna be a runtime error, and an error will be thrown by the parsing of that JSON. So, where did my slides go there? Okay. Actually, this was the setting I needed to change, use unknown catch in variables, not strict, which I just changed. That's why I didn't get the behavior I was expecting. I guess I should read my own slides. So, an example on how to do it, I actually added some fancy formatting here, which is pretty neat. With %c, you can have a second argument to console.error, or console.log, or one, for that matter. And here you can specify a CSS style of how you want the error message to be printed. Let me actually copy this and put it in, because it does look pretty neat. So, we'll copy this and replace this with that. And now, if I use the invalid URL, you see that... My zoomed skills leave a bit to be desired at the moment, but now you can see here that's the error is nice, that's the error is nice and red and bold and larger. So it stands out nice. Nothing to do with TypeScript, but just one of those things a lot of people don't know and it's actually pretty neat. So basically, at percentage C as the start of the first string, which means that the browser knows that the seconds arguments to console.log or console.error, et cetera, is a CSS string, which will be used to format the first message. Neat. So let's go and do this. Add the try catch, play around a bit with the error. See that the error can be all sorts of things. And after that we'll go back to TypeScript, type mappings and play around with some more useful type mappings there. We'll actually solve a few more bugs in code. So let's continue. In the next step, we're gonna do some type mapping and specifically I'm gonna look at read only but there is a lot of type mapping and map types in TypeScript and I'll show some more examples. But first let me actually show you why I care about read only. I've got the application here. Oh, I've still got the pro conversion of the application. Where was that URL? Here, let me quickly fix that. So I've got the application and I can add pizzas and there are extra toppings I can add. So pepperoni pizza is 12.50 and then I added extra topping of cheese and pepperoni. So that's another 50 cents and another 75 cents. So that should be 13.75. And indeed it is. And actually my son also wants one, so I'll add another but all of a sudden the price has bumped to 14.50. What gives? Something is wrong and let's add another pizza here. I'll add three toppings. So that's 14 plus cheese, plus pepperoni, plus green bell peppers. So that should be 15.75 if I add that up correctly. But apparently I don't because it's 16.50. And let's add another. And now all of a sudden that same pizza with the same toppings is 17 euros. And if I add one more, it's still 17 euros. But something is going wrong with adding toppings and the prices of those. Now there are no compile errors, but TypeScript can help us find this and can tell us exactly where things go wrong. Because let's go to the slides. With map types, we can start doing all sorts of things. And there are a whole bunch of built-in map types. I've listed a few here, but the whole list is much larger. But you can turn types into read-only types, which means properties can't be set. And that's the one I'll actually be using. You can create your own customer mappings. I'll do that as well later. You can use other ones like the zot.infer. That's a custom mapping where you map a type of a schema to a TypeScript type. Deep read-only, that's one which I'll create later.

13. Using the Read-Only Type Mapping

Short description:

The read-only type mapping in TypeScript can change an object from a read-write type to a read-only type. By adding the 'read-only' keyword to the type definition, the properties become read-only. This can help prevent unintended changes to the object's properties. In the example provided, a compile error was caused by using a single equals sign instead of a double or triple equals sign, resulting in the incorrect modification of the price of toppings. By fixing the comparison and using the read-only type, the issue was resolved.

That's not one I came up with myself, but it's actually a pretty simple type to create and pretty useful. But first let me show you how read-only is gonna help us fix this error. So the problem is by default, types of objects are read-write because that's the way JavaScript works. If you create an object, you can read from it, but you can write to it. You can add new properties to it. If you've got an array, you can change properties in there. You can push new ones. You can pop ones from it. Well, the read-only type mapping will change that from a type which is read-write to it being read-only.

So what I can do is I can go into types and I can say, well, we've got this extra ingredients and that's an infer of that, but I actually want this to be a read-only, like this. So if I look at the type definition now you can see here the keyword read-only appears which wasn't there before. So if a look at pizza, you can see all the properties are just listed there. But if I add this second read-only to the pizza and I take a look at type, you can see read-only appear. Now, immediately main.ts turns red and I can see down here there is a compile error. There was this little red line there showing me there was a compile error. And the compile error points me to this.

So I was doing the loop there and I was looking for elements which have, or extra toppings which have a price of one. But instead of using double or triple equals as a comparisons by accident, have a single equals here, which is an assignment. Now, that's perfectly fine in JavaScript, if you want to assign something inside of an if statement. So as a result, it's perfectly fine in TypeScript. But it's not what I intended. And that's what's actually changing the price of the toppings as soon as they're selected. If I add a topping to an order, it's prices changed from whatever it was to one euro. So I can fix this, make this a triple equal comparison, go back to the application, add a pepperoni with two toppings, it's the 1,375, which we expect. I add it again, it's still 1,375 because the toppings haven't changed price. I do the same with the next one. It should have been 1,575 the first time, which previously it wasn't, but now it is 1,575. And if I added again, same things happen.

14. Exploring the Use of Read-Only Types

Short description:

In this section, we explore the use of read-only types in TypeScript. We discuss how read-only types can prevent object mutation and ensure data integrity. We demonstrate the use of read-only types in function parameters and return types, as well as their application in loading data from external sources. Additionally, we highlight the benefits of using read-only types throughout an application to enforce immutability. Finally, we mention the limitations of read-only types and introduce the concept of deep read-only types as a solution to further enhance immutability.

Now, where do I use Read Only? I now defines the types here as Read Only. That works, but let me revert this for the pizza for a moment. What I typically do is I define my types and then I go to my application and I determine, well, how am I using them? Suppose I've got a function, function order pizza, it takes a pizza, off type pizza, and it should print the name. So console.log Hawaii. But we don't actually have a Hawaii pizza. So suppose I call this order pizza function and I pass in a pizza object. And let's see, it needs a name. We'll do something very different pepperoni. It needs a price. What's that slash there? So it needs some price. It needs some image URL, credit some extras, and then that should be perfectly happy. Now, if I check the browser, the console log. Why doesn't it zoom in here? It is Hawaii even though I very much pushed in a Pizza Pepperoni. So we've got a bug. Now we might tell a developer, oh, we've got a bug. And let's say we've got a really annoying colleague. You might say something like this, is Hawaii? Okay, bug fixed is because by the time we print the pizza name, it is always Hawaii. So the object we're passing in is being mutated on the fly. And if I look at disorder pizza, there is no reason to assume that this name is gonna change in there. So what I think is much better definition here is saying we're passing in pizza, but it's a read-only pizza. I'm without a question mark. I'm explicitly declaring I'm not going to change the pizza in there, I'm not going to change anything about that pizza. So this isn't valid. I've got to compile error. I can't assign to name because it's a read-only property. So now, if I want to fix the bug properly, I have to remove this and say Of course, now it's going to print pepperoni here, just like it should. So that's one place I use it. With input parameters, especially if they're objects, to indicate, okay, I'm not going to mutate it. If I hover over this, I can say, see, it takes a read-only pizza. I know it's not going to mutate it. Another place would be something like here. I'm loading pizzas and I'm returning a promise of pizzas. But this application doesn't own this data. It returns this data from a server. In this case, it's just a JSON file. But it's outside of the scope of this. So I might say, well, I'm not going to return a pizza array. I'm going to return a read-only of the pizza array. It helps if I type it correctly. So like this. I've got a function which says, I've returned a promise of read-only pizza arrays. Here, I know that the pizzas are read-only. Render menu is pasted but it knows it should not change it. Right now, it could. I could do something like, is how I. Assuming that this is actually an object. So let's test for that. So this is perfectly happy, but I've got this Compile error saying, well, I'm passing pizzas in but they should be read-only. I can go here and say, well, this really is a read-only array. And this way it kind of propagates. Now this doesn't produce an error yet. I'll come back to that in a moment. But this is kind of how I fix these. This case, I'm actually going to put read-only back here on the different types. So everything is read-only, except that. This should be read-only array. This should also be read-only, because we're not going to change those. So for the whole application, they are read-only. And now this does produce a compile error, because that pizza is read-only. So let me comment this out and make sure the application runs just fine. So here are the changes I made. In this case, I actually used read-only array, which is a slightly enhanced type. But read-only, often pizza array will work as well. The way I changed the render menu, I'm not even sure what my change here was. So the fix I had to do for that extra price, not assigning one to it, but checking against one, which was the intent, which caused the compile error due to read-only. So please go and do this. And then next step, I'll show you how we can actually make read-only even better, but we'll keep that for the next step. Let's go and do the next step, where we're gonna create a custom-type mapping, deep read-only, because read-only is really nice and it solves a lot of problems, but not all of them. And first, let me show you something it doesn't solve, because this is something I couldn't do anymore, because the pizza was read-only, so I can't change the name.

15. Using the Deep Read-Only Utility

Short description:

By using the deep read-only utility, we can make our code much more type-safe and prevent unwanted modifications. This utility allows us to create a read-only type that applies recursively to all properties and nested objects. By applying this utility to our code, we can catch compile errors when trying to modify read-only properties or arrays. This utility was derived from the work of Basserat, a YouTube content creator who provides concise and focused videos on TypeScript and related topics. It is highly recommended to explore the use of this utility and its impact on code safety and reliability.

So comment that out, prevent that compile error. But if I did the same with the extras, I can actually start doing something. I could push into this and add new value in there, whatever, a pizza with some whatever, or I could say get the first extra, and give it a new name. So that's just a string array, if I'm not mistaken, yep. So we'll just assign to it, oops! No compile errors here, nothing. And if I go back to the codes here, we can now see right here that we've got oops as a topping and whatever. No runtime errors because we already had that unbounce array check. So it falls back to the defaults, which had an empty price, at least the price of zero. So at least or no runtime errors there. But we kinda want to prevent this, and it turns out, that's actually not that hard to do. If I go back to the types and I select read-only, and I press F12 to go to the definition, we can see what the definition of read-only looks like. It takes in the type, so that T argument, that's kind of a convention in generics to use called types T, not very descriptive, and I would actually prefer to use a slightly different name, but that's what they used here. And they kind of loop over it here, saying that for all the properties or the keys in the object type, we'll create the same property except with read-only in front of it. Do similar things with say, with partial day map over it, and they use question mark there or a character in front. So they make it optional or require it with win question mark. They remove the optional part. But we're interested in read-only. So let me copy this and we'll turn this into a deep read-only. Paste this in here and we'll call this deep read-only cause we want to make this smarter. And instead of using read-only, I'm just gonna call this deep read-only here for the pizza. Now so far, I've only made a copy, given it a different name. So the behavior is exactly the same. But what I can do is I can do this recursively. So I can say, well, the type of the property is not just the property but it's a read-only of that property. So the property itself can't be assigned but anything inside of it becomes read-only as well. And this kind of keeps going deeper and deeper as long as there is an object structure. And now if I go back to main.ts all of a sudden we see I can't do a push anymore. So there is a compile error here and there was a compile error in this assignment. Push doesn't work because push doesn't exist on a read-only array. You can't push or shift or unshift or any of these things on it. You can still loop over it. You can do a map or a for each. That's perfectly fine. That doesn't change the original array. But anything which changes the original array won't work. Same here, I tried to assign something, doesn't work. And even if this would be a rich object I would try, in this case it's a string so that wouldn't work anyway, but it would still say, okay, that doesn't work. Let's remove that and comment these out. And I can actually go and say, well, that whole pizza array could be a deep root only, except in this case pizza is already deep root only. This makes the array read only, so we're kind of done there. But I could say, well, all of these ingredients are deep root only. Again, I could remove this and that whole structure would be completely root only. So if I go back and where I was at triple equals one. There, if I change this back, it's not root only, why is that? Didn't I make the right? That's interesting. I was expecting this to be deep root only, but for some reason it isn't. I still need to add it. Here to get the same behavior back. Now this is a compile error again. This sometimes happens when things are nested with inferring, but in this case I would have expected this to keep on working all the way down. Maybe that's something with zinferr, which kind of defeats this. So a structure like deep root only is really nice. Now I didn't come up with this myself. Let me go back to the slides. The sources here, it's from this guy called Basserat. I don't know what his real name is, but he came up with this. It's a pretty interesting guy to follow. He does a lot of stuff for YouTube, this channel. He makes lots of short videos on YouTube about how to do things like you'll see videos of two minutes, five minutes, stuff like that. All very much focused on one thing, like should you type in for type or an interface in the pros and cons. Highly recommended stuff from him. So that's where I got this from. You'll actually see more complex implementations on the internet which worry about whether something is an array, et cetera, but it turns out this will just work perfectly fine with a race. So the deeper, you'd only type add it and add it to the different parts. So please go and do this and see the effect on what you can change in the pizza or the extra ingredients between just using read only like I did initially and using a deep read only utility like that. It's kind of like amazing how simple it is to make things much more type safe with a small utility like this. We'll see some more small utilities like that.

16. Using Indexed Access Types and Other Type Mappings

Short description:

With indexed access types, you can access specific properties from a type definition. This allows you to keep properties in sync and have a single source of truth. Indexed access types are useful when working with nested types from NPM libraries that don't expose all the types. Additionally, TypeScript provides other related functions like parameters map type, return type map types, and constructor parameters map type, which allow you to extract parameter and return types from functions and constructor parameters from classes. These options are convenient for extracting types.

We'll see some more small utilities like that. So the next step I'm gonna fix another issue in the code base. And let me show you the issue first. I've got the definition of a pizza and that pizza is derived from that pizza schema. And it says, well, the name is a string, the price is a number, et cetera. And then I've got items ordered which are pizzas because this is a pizza shop. And again, I've got name, you string, prices, a number. And that's kind of fine, except if I would go into the schema and say, well, the name, or let's change the price, that's not going to be a number, that's going to be a big int, which is probably not really appropriate for a price of a pizza, but let's assume we've got some really expensive pizzas. Now, there will be some errors here. This is actually a result, I think, of that zero being a number. So now it says, well, we can't add a big int and a number or a big int. So apparently there is still, the price here is a number. So it's not that one I should be fixing, it's a different one. And here there is an error saying, well, big int, number, mismatch. So I would kind of want to keep the price here and the price in the items order the same. Now, of course I could go in and say, this should also be a big int, and then they're the same, but it's kind of like I have to do two changes and keep them in sync and then deal with any potential other error. And I don't really want to do that. If the name changes, if the price changes, I want to have a single source of truth. So with indexed access types, what you can do is you can take a type definition and you can index into that just like you could with an object to get its values, well in exactly the same way you can access into that with types. So let's actually just do it. I've got the pizza type here, it has a name. So I could say, instead of the determining what it should be, I can say just grab the name property from that and whatever name will be, in this case it's a string, is what's gonna be used right here. I can do the same here, say pizza with the price. And now price is of type big int. But if I go back to the schema, say, this should be a number again, I go back to my items ordered, I look at price and it's also a number. So they're automatically kept in sync. The pizza determines the shape. Now in here I could actually go in and change it. But there is another place where this comes in really handy and that's if you're using some kind of NPM library, which uses types, but doesn't expose all of them. It might expose to main types, but not all of the nested types. So you might get some big object and that has nested objects, et cetera. And you want to pass the nested object into a function, but they don't provide the type, they didn't export it. Of course you can recreate the type, but then it's kind of like, well, if the first of that library updates, there might be a mismatch, et cetera. And you kind of have to do that manually. Well, this way you can say, well, I've got the main type from whatever they're providing and I'll extract the subtypes from that. If this was an asset structure, I could even go in here like this, a price toddle value, of course, that doesn't exist because the price is just a number. But if it was a richer object, I could do that. So that's really convenient. There are a couple of other related functions I'm not going to use, but I want to mention. That's the parameters map type, the return type map types and the constructor parameters map type. Basically with the parameters and the return type, you're passing the type of some function and the parameter will return an array of all the parameters defined in that function. So again, if you import a function from an NPM library, they didn't expose the parameter types, you can get them this way. They didn't expose to return type but you want to use that somehow. Well, you can map it from the type of the function that way. They export some class, but you need to constructor, well, use constructor parameters with the type of the class and you will get everything, you need an array of parameters you need to pass into that constructor. So these options are really convenient to extract types.

17. Type Mappings in TypeScript

Short description:

In TypeScript, there are two type mappings, omit and pick, which can be used to specify a type. Omit allows you to start with a type and omit certain properties, leaving you with the rest. Pick allows you to specify exactly which properties you want. Type composition can be used to add additional type definitions to the result. Whether to use omit or pick depends on the specific use case. Omit is useful when you want to exclude specific properties from a type, while pick is more suitable when you want to be specific about the properties you want to include. There are other useful functions in TypeScript, such as exclude and extract, which allow you to work with individual types and return specific types based on conditions. By using omit or pick, you can achieve the desired type mappings in your code.

So question will be finished in half an hour. Yes, we're going to stop in half an hour. We're not going to be done with all the material but that's when the workshop ends. So we're just going to be done. You've got the material for the rest of the slides to look at, so you can do that yourself.

So here's that mapping I just added and that's basically all the risk to keep those in sync. So let's look at another way of doing type mappings which we can use to achieve the same results. As I mentioned TypeScript has lots of type mappings and are two which you can use to specify a type like these order items. And you can either do that with omit or pick and they are both kind of similar but this name suggests with omit you start with the type a new omits certain other properties and you'll be left with whatever else was there. And with big, you specify exactly which you want. So the thing I could do is I could say well, these item orders are actually gonna be done slightly different. We're gonna do a type mapping export type, not like that. Item's orders is, then we're going to say I want to pick certain properties from the pizza type. There and which do I want? I want the name and the price. So whatever is on pizza, I'll left with just those two. And because on pizza, name and price are read-only on items or orders, name and price are also automatically read-only. It's just copied. But I also wanted these extra ingredients. So still need to add them to them. Well, we can use type composition. So I can basically say, add to this an additional type definition which just adds those extra ingredients, and now if I look at the items orders, it doesn't look very nice. But the result is almost the same because now name and price are read-only. But all the other stuff is there. The lack of readability is a bit of a thing with these.

Now I could do the same with omit. So let me copy this and do this in a slightly different way. So instead of pick, I could say, omit. Now if I look at items ordered, it's a little hard to see, but it would actually be the imageURL, image credit, and the extras with two definitions because I'm omitting name and price. So what I would want to do is omit imageURL, image credits, or extras. There it is. IntelliSense isn't quite working. But IntelliSense output exactly the same result. So whether you use omit or pick kind of depends on your case. In this case, I think pick makes more sense because if I'm going to add more properties to the pizza, maybe it's a popularity rating there added by customers, it shouldn't automatically go to those item orders. I want to be very particular. OK. I want to name price and the extras there shape. So I would prefer that. But there are other cases where you say I want everything from a specific type but leave out one specific thing. Like I want a person and I want everything about the person except his salary. So I would use omit and then take the person and omit the salary from there. And then I've got just the plain person without financial data. So it really depends on your use case. There are some other useful functions which I'm not going to use, but I just wanted to point out. Omit and pick will let you work on objects, pick properties from there, or to find properties of the object type, I should say. Exclude extract will let you do stuff with individual types on something. So I might have a type like this, which is string or number. So if I define a variable string or number, I could assign one to it or my name, but not true because that's not a string or a number. And then I can say, we'll take strict or number and exclude number. And that just leaves me with string. And I could have a whole list of types here and I could say exclude number and dates for instance and just return types like that, which is pretty neat. And if I would say here exclude number or string which basically means I excluded everything, I would end up with never type, which is an interesting type because there is nothing left. So it can never be used. That wasn't supposed to happen. I went a bit too fast. Let's go back. Yeah, that's where I was. So let's go and implement this. Use either pick or omit what you prefer or play around with both. I think pick makes more sense in this case but you can achieve exactly the same result with either of them. So take your pick.

So let's take a look at the small issue we now have. And that's the fact that if I just hover over that items order, it's kind of hard to see what this type really is. I can see there was a big deep read only of some type with the name, price, image URL, image credits and extras. And then there was another extras being added to that and to a something with name price here. So, it's pretty hard to figure out what the items order exactly looked like. And that's not just here. If over here, I check, I see exactly the same thing. Now I could start typing and kind of see, well, okay, I didn't have an extra. So what's wrong here? And then somewhere it says, okay, extras is declared.

18. Improving Type Readability and Type Predicates

Short description:

To make types much easier to read, a resolve function can be used to echo out the same property without any mapping. By wrapping the pick and type union inside the resolve function, the resulting type becomes much more readable. The resolve function is not part of the standard TypeScript definitions, but it greatly improves the readability of types. Opaque types are another useful feature that can be used to differentiate between similar types based on their business intent. Type predicate functions can be used to check the type of a value at runtime and perform specific actions based on the result.

Okay, so I need extras. What does it look like? Well, it shows up here and here. So apparently it's an array, but what kind of array? You kind of have to go and hunt through that definition to figure out what you should be using. It turns out there is a little generic, which you can add, which is pretty simple to use. And it's in this ebook,, it's from Dan van de Kamb, and he was actually a speaker at the conference talking about using TypeScript types and databases and how to deal with that. Pretty interesting session, highly recommend it.

And using that resolve function, we can make stuff a lot more readable and I'm not actually gonna go to that post because you kind of have to hunt through it. I'm just gonna take it from my own code. Let's see. So it's opened up on my other screen. Let's see if I did things right. I copied it, yep. So I've got the resolve type here and basically it says, well, loop through all the properties in the type, just like we did with the deeper read only. But in this case, we're not even gonna do anything with it. We're just gonna echo out that same property. So the same type and everything. The only exception is if the type extends function, then we're just gonna return that function itself, the original type. We're not gonna do any mapping with it. In this case, we wouldn't even need it, but that makes it a bit more generic. So if I take this resolve and now say, I wanna resolve this whole thing, this whole type. So all I've done is wrapped the whole pick and type union inside of resolve. If I now look at items ordered, it's very readable. I can see there is a name, price and extras, and I can see how clearly what types are there. And I can even see, okay, the name and the price are read only, but the extras are not read only. So that was completely not clear before. So I might wanna say, well, I wanna define that as read only as well. So now all of them are read only. And the same, if I go back here, I hope for over that same items order, it resolves exactly the same way. So it's a small thing. And it's kind of like, I wish that was part of the standard TypeScript definitions and TypeScript would just do this because it makes types much easier to read instead of showing how a type was constructed, it shows how a type is actually, how it ends up, I should say. So much, much easier.

So, please add that, just, well, it's simple enough to type in, but you can copy it from the source if you want to, and make sure that that item order looks exactly like this with the read-only. So I'm gonna open up the breakout rooms. This is also gonna be the last thing we do. The last thing we do, it's almost 7 o'clock, at least 7 o'clock my time. So we've just got eight minutes left. There are a couple of more things in the slide deck and the exercises, which are interesting, but we won't have time to do it. I'll very briefly talk about it after this exercise. When that's done, we're gonna conclude and wrap things up. So see you all in four minutes after making this change and seeing the effects on the type mapping results.

So that's everyone back. So let's wrap things up cause we're out of time. I did have a few exercises left. So I'm briefly gonna, very briefly, gonna tell you about it. Opaque types are really nice. Turns out there's another book in the application because account and amount are switched. They're both numbers so TypeScript thinks, well number to number parameter is perfectly fine. And it doesn't realize that their type of number or the intent, the business intent with the number is completely different. So with opaque types, and that's a type like this, you can actually create this type which derives this place from number but adds some key to it. And so the amount, we add the key amount which account we add to key account. Then we've got types which are actually different but will still also work as a number. So we can do something like this. Change the function definition from amount and account from numbers to amount and accounts because in the actual call to the checkout function these two are flipped. And in that case, if you type those as well TypeScript is gonna warn you saying well, you can't assign an account to an amount and the other way around. So, you use costs here. Now, costs are nice but they kind of, well, work at compile time but only compile time. So, a slightly better way is to start using type predicates functions. And a type predicate function looks like this. So, the function is account which takes some value. And the important part is this, the result value is account. So, if this returns true, then whatever was passed in is considered to be of type account. And if it's false then it's not considered to be of type account. So, you can do whatever you want. I'm checking the type. So, is type a number and is the length of the number 10 characters then it's considered an account number, otherwise it isn't. And with that, you can do something like this. So, at runtime, you can check whether it's an account. This will be used both at compile time so, if what the type passed into account is a number then it's at runtime gonna say, well, okay, then it could match that check. So in here, sorry, at the compile time, it's gonna say, well, if it passes that check then here account's amount. No, sorry, account must be the right type and otherwise we can throw an error.

19. Type Safety and Enhancements

Short description:

Type assertions are a more powerful way to ensure type safety. By using type assertion functions, you can assert that a value is of a specific type and throw an error if it isn't. This provides both compile time and runtime type safety. Exhaustiveness testing using the never type is another useful feature to ensure all possible cases are handled. By combining these type safety features and using strict settings, validating data at boundaries, and creating custom type mappings, you can enhance the reliability of your TypeScript code. Thank you for attending the workshop and feel free to ask any questions.

So, we get type safety both at compile and at runtime. Which is really nice. But you still kind of have to worry about things not matching, just like with a question mark dot operator, optional chaining. Like, if something isn't what it is at runtime, it would just be ignored. So you have to cater for that.

Let's go to the next one, not to the exercise. A slightly more powerful way, at least in the low scenario, is doing type assertions. So type assertion functions are very simple, but now the result changes not from value is account but the value, asserts that the value is account. And these functions throw an error if whatever you assert isn't the case. So that simplifies use case a bit. I can say, count is this number, amount is this sum, assert amounts to be a valid amount, assert account to be a valid account. And after that I can use these variables both as they were originally numbers, but also as the amount and account type. And again, both run time and compile time type safety because compile time it knows you will only get here if these types match. And at the one time you will get an exception, an error being thrown if it doesn't match up. So pretty nice type safety.

Another nice type safety feature is exhaustiveness testing where you can actually use the never type to ensure that you don't get to a specific case. So something like this, we've got an enum with a couple of animals and we've got switch statements with cases for the different animals. And in the default I call assert never with whatever the animal is. And if all the types, the possible types are handled animal here is going to be never. So I can actually call this function where it's assigned to never. But suppose I would add another type or leave out bird here, then over here, animal would be bird and you can't assign it to never. So again, compile time safety. No runtime guarantee. So I've got this throw new error for the runtime behavior.

So we have compile time and runtime type safety again. But again, we're not going to do that because we've run out of time. So I hope I've shown you some useful TypeScript settings and tricks and type mappings to make things better. You saw the standard strict setting which I'm guessing all of you were already familiar with but we've seen that there are a whole bunch of additional strict setting which are not influenced by strict itself which I would recommend you turn on as well. I recommend you validate all data at the boundaries not just from users, but also from APIs by using something like Zolt or IOTS or Yelp or many libraries there or just write some custom code. Use the type inferencing features available for many of these libraries to create your TypeScript types based on the same types. Use type predicates assertions like I just showed you in the last slide. Use map types to create new types. Create custom type mappings for your own benefit. It's pretty simple but pretty powerful. But getting started with it is really simple. It's kind of interesting to see that the TypeScript type system is a programming language by itself which is pretty neat I think. So with that, I'm gonna wrap it up. Only four minutes over time. Of course we did skip a bit of material but that's the way things go. Well I thank you all for attending. If you've got any questions, feel free to ask.

Watch more workshops on topic

React Summit 2023React Summit 2023
170 min
React Performance Debugging Masterclass
Featured WorkshopFree
Ivan’s first attempts at performance debugging were chaotic. He would see a slow interaction, try a random optimization, see that it didn't help, and keep trying other optimizations until he found the right one (or gave up).
Back then, Ivan didn’t know how to use performance devtools well. He would do a recording in Chrome DevTools or React Profiler, poke around it, try clicking random things, and then close it in frustration a few minutes later. Now, Ivan knows exactly where and what to look for. And in this workshop, Ivan will teach you that too.
Here’s how this is going to work. We’ll take a slow app → debug it (using tools like Chrome DevTools, React Profiler, and why-did-you-render) → pinpoint the bottleneck → and then repeat, several times more. We won’t talk about the solutions (in 90% of the cases, it’s just the ol’ regular useMemo() or memo()). But we’ll talk about everything that comes before – and learn how to analyze any React performance problem, step by step.
(Note: This workshop is best suited for engineers who are already familiar with how useMemo() and memo() work – but want to get better at using the performance tools around React. Also, we’ll be covering interaction performance, not load speed, so you won’t hear a word about Lighthouse 🤐)
React Summit Remote Edition 2021React Summit Remote Edition 2021
177 min
React Hooks Tips Only the Pros Know
Featured Workshop
The addition of the hooks API to React was quite a major change. Before hooks most components had to be class based. Now, with hooks, these are often much simpler functional components. Hooks can be really simple to use. Almost deceptively simple. Because there are still plenty of ways you can mess up with hooks. And it often turns out there are many ways where you can improve your components a better understanding of how each React hook can be used.
You will learn all about the pros and cons of the various hooks. You will learn when to use useState() versus useReducer(). We will look at using useContext() efficiently. You will see when to use useLayoutEffect() and when useEffect() is better.

React Advanced Conference 2021React Advanced Conference 2021
174 min
React, TypeScript, and TDD
Featured WorkshopFree
ReactJS is wildly popular and thus wildly supported. TypeScript is increasingly popular, and thus increasingly supported.
The two together? Not as much. Given that they both change quickly, it's hard to find accurate learning materials.
React+TypeScript, with JetBrains IDEs? That three-part combination is the topic of this series. We'll show a little about a lot. Meaning, the key steps to getting productive, in the IDE, for React projects using TypeScript. Along the way we'll show test-driven development and emphasize tips-and-tricks in the IDE.

React Summit 2023React Summit 2023
151 min
Designing Effective Tests With React Testing Library
Featured Workshop
React Testing Library is a great framework for React component tests because there are a lot of questions it answers for you, so you don’t need to worry about those questions. But that doesn’t mean testing is easy. There are still a lot of questions you have to figure out for yourself: How many component tests should you write vs end-to-end tests or lower-level unit tests? How can you test a certain line of code that is tricky to test? And what in the world are you supposed to do about that persistent act() warning?
In this three-hour workshop we’ll introduce React Testing Library along with a mental model for how to think about designing your component tests. This mental model will help you see how to test each bit of logic, whether or not to mock dependencies, and will help improve the design of your components. You’ll walk away with the tools, techniques, and principles you need to implement low-cost, high-value component tests.
Table of contents
- The different kinds of React application tests, and where component tests fit in
- A mental model for thinking about the inputs and outputs of the components you test
- Options for selecting DOM elements to verify and interact with them
- The value of mocks and why they shouldn’t be avoided
- The challenges with asynchrony in RTL tests and how to handle them
- Familiarity with building applications with React
- Basic experience writing automated tests with Jest or another unit testing framework
- You do not need any experience with React Testing Library
- Machine setup: Node LTS, Yarn
React Advanced Conference 2022React Advanced Conference 2022
148 min
Best Practices and Advanced TypeScript Tips for React Developers
Featured Workshop
Are you a React developer trying to get the most benefits from TypeScript? Then this is the workshop for you.
In this interactive workshop, we will start at the basics and examine the pros and cons of different ways you can declare React components using TypeScript. After that we will move to more advanced concepts where we will go beyond the strict setting of TypeScript. You will learn when to use types like any, unknown and never. We will explore the use of type predicates, guards and exhaustive checking. You will learn about the built-in mapped types as well as how to create your own new type map utilities. And we will start programming in the TypeScript type system using conditional types and type inferring.
React Day Berlin 2022React Day Berlin 2022
53 min
Next.js 13: Data Fetching Strategies
- Introduction
- Prerequisites for the workshop
- Fetching strategies: fundamentals
- Fetching strategies – hands-on: fetch API, cache (static VS dynamic), revalidate, suspense (parallel data fetching)
- Test your build and serve it on Vercel
- Future: Server components VS Client components
- Workshop easter egg (unrelated to the topic, calling out accessibility)
- Wrapping up

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

React Advanced Conference 2022React Advanced Conference 2022
25 min
A Guide to React Rendering Behavior
React is a library for "rendering" UI from components, but many users find themselves confused about how React rendering actually works. What do terms like "rendering", "reconciliation", "Fibers", and "committing" actually mean? When do renders happen? How does Context affect rendering, and how do libraries like Redux cause updates? In this talk, we'll clear up the confusion and provide a solid foundation for understanding when, why, and how React renders. We'll look at: - What "rendering" actually is - How React queues renders and the standard rendering behavior - How keys and component types are used in rendering - Techniques for optimizing render performance - How context usage affects rendering behavior| - How external libraries tie into React rendering
React Advanced Conference 2021React Advanced Conference 2021
39 min
Don't Solve Problems, Eliminate Them
Humans are natural problem solvers and we're good enough at it that we've survived over the centuries and become the dominant species of the planet. Because we're so good at it, we sometimes become problem seekers too–looking for problems we can solve. Those who most successfully accomplish their goals are the problem eliminators. Let's talk about the distinction between solving and eliminating problems with examples from inside and outside the coding world.

React Advanced Conference 2022React Advanced Conference 2022
30 min
Using useEffect Effectively
Can useEffect affect your codebase negatively? From fetching data to fighting with imperative APIs, side effects are one of the biggest sources of frustration in web app development. And let’s be honest, putting everything in useEffect hooks doesn’t help much. In this talk, we'll demystify the useEffect hook and get a better understanding of when (and when not) to use it, as well as discover how declarative effects can make effect management more maintainable in even the most complex React apps.
React Advanced Conference 2021React Advanced Conference 2021
47 min
Design Systems: Walking the Line Between Flexibility and Consistency
Design systems aim to bring consistency to a brand's design and make the UI development productive. Component libraries with well-thought API can make this a breeze. But, sometimes an API choice can accidentally overstep and slow the team down! There's a balance there... somewhere. Let's explore some of the problems and possible creative solutions.

React Summit 2023React Summit 2023
23 min
React Concurrency, Explained
React 18! Concurrent features! You might’ve already tried the new APIs like useTransition, or you might’ve just heard of them. But do you know how React 18 achieves the performance wins it brings with itself? In this talk, let’s peek under the hood of React 18’s performance features: - How React 18 lowers the time your page stays frozen (aka TBT) - What exactly happens in the main thread when you run useTransition() - What’s the catch with the improvements (there’s no free cake!), and why Vue.js and Preact straight refused to ship anything similar