Flowtype Fundamentals


  1. Find a Place for Types and Flow Welcome Welcome to this course on Flow. This course will help you up your game when it comes to creating new JavaScript projects. My name is Jake Trent and I'm glad to be learning these concepts with you. Have you ever been introduced to a JavaScript project where you've wondered how to use many of the APIs that previous developers had written? Have you ever created a large JavaScript code base yourself and found that as it grew, you were losing your grip on how it worked? In both situations, your confidence waned. Well, we want to get our confidence back up as a developer of great JavaScript applications. Inside our programs, let's take a look at types of data types, in general, and see how they can help and let's see how Flowtype specifically brings some of these benefits to use in the JavaScript world. Perhaps there's a place for types and Flowtype in your development process. Let's find out together.

  2. What Are Types? So what are types? Do they matter? A type, otherwise known as a data type, is a classification that is assigned to data that a computer programming language will use to understand developer intent and how to treat that data. Generally, you could say some types are primitive types. Examples of these are numbers, strings, and Booleans. Other types are more complex called composite types and examples of these could be objects, sets, and arrays. So do types matter? In this example, how should a programmer interpret this type? Is that important? And how should the program or the computer interpret this type?

  3. Categories of Types There are several categories of types that will be useful in framing what we start with in JavaScript and what we can eventually accomplish. These are some terms that you might have heard before. Static types are those that are determined at compile time. Type information is gathered and validated then before the program is run, and if that type check passes, then you run your program. Dynamic types are where type information exists in the program, but these types are not checked until runtime. The interpreter assigns types to variables based on their values at the time. Operations between mismatched types will be invalid at runtime. Strong types and lose types have more in-precise definitions. Strong type could be interpreted to mean a stricter assignment of types. There is an expectation of closer matches to the types when checked. Loose types have looser rules. Your program may attempt implicit conversions and this can produce unpredictable results. So where does JavaScript land in relation to these terms? Well, it's a dynamic loosely-typed language, meaning that types are discovered at runtime and implicit conversions are common. Flow is a tool that provides a very different category of types on top of your JavaScript, strong static types that are validated at compile time and that are required to be precise.

  4. JavaScript's Existing Types Does JavaScript have existing types? Some people say that JavaScript doesn't have any types, but if you think about it for a moment, it's no surprise that JavaScript needs to have some types to express developer intent at least to the interpreter, it just doesn't have very many. In JavaScript, there are six primitive types, Boolean, which is a logical true or false, null, which is a value that intentionally points to nothing, undefined, which is an unassigned variable value, number, which contains all numeric values floating point and whole number, string, which is textural data, and symbol, new to ES6, which is used to represent unique immutable primitives. There is one object type, which is a data structure that can contain data and instructions. Sometimes a limited set of types is desirable. It can make the language more approachable and forgiving, but that initial forgiveness can end up feeling like sloppiness or apathy in the longer term. Given the increasing size and complexity of JavaScript projects in recent years, developers often wish for better tools and that's where Flow comes in.

  5. What Is Flow? So what is Flow? Sounds like Flow is going to give us more types, but how does Flow make anything better for JavaScript and for our projects. It becomes a layer, a superset of JavaScript or additional syntax that is written on top of your JavaScript code. Type information is added via annotations, they look like this. Where annotations are now always explicitly recorded by a developer, Flow will use type inference to determine what variables are typed as. Flow's type inference is more powerful than the JS interpreters because of its strong interpretation of types. Flow is a tool that supports gradual typing, which means we can add as much or as little type information to our program as we desire or believe is helpful or want to maintain. Flow when run will then provide feedback on type inconsistencies that it finds and report these errors back to you as the developer. You are able to then find and fix those problems and increase the quality and correctness of your programs.

  6. Origins of Flowtype Where then did flow come from and why does it act like it does? Flow is a tool that was developed at Facebook and why did they make it? In 2011, React became big at the company. More people were interested in building JavaScript and the JavaScript code bases grew. They quickly became intimidating where there are not many ways to reason about JavaScript code like they did in other languages. So they looked for a tool that would allow them to analyze their code base and make the developer experience better and Flow was born. Today, Flow can do static type analysis. In the future, Flow is seen as a platform for other tools optimizing compilers, editor integrations, deep refactoring tools, and security analysis. Of course, this is in the future and yet to be seen. For the situation at Facebook, this was their answer for an improvement in tooling, and now, happily, we can use it as well.

  7. Flow Compared to Others There are surely other tools that could help us accomplish similar goals of type safety, code readability, and developer experience so let's look at a couple. Flow's closest competitor is probably TypeScript. It's a much larger superset than Flow in that it adds much more than support for types on top of JavaScript. It still compiles back to JavaScript like many other languages, but this doesn't guarantee that it will closely track current JavaScript or JavaScript proposals or an evolution over time. The language may diverge further. It is supported by Microsoft, it is an active development, and has a lot of momentum. TypeScript has more standards and support around sharing types in the library community. And the tooling support for TypeScript is solid and getting better all the time. And in the details, Flow and TypeScript do approach support of certain features differently. There are other compiled to JavaScript languages which have additional type safety as a founding goal. A couple notable examples are ReasonML and Elm. These languages are vastly different from JavaScript. They have their roots and functional programming paradigms, and they each compile back into JavaScript. Looking back at Flow, it differentiates itself by just being type annotations that are compatible with JavaScript. Thus, it becomes the prime option for adding types and staying in JavaScript. It's also a gradual option where you can implement it on top of JavaScript to the extent you desire. You don't need to buy into it all at once or start over in a new language in order to learn Flow. So should I use Flow? Well, only you can answer that. There are many other type languages, but you might use Flow when you want to remain in JavaScript land. That might be because you're highly invested in the language, proficient in it, or are able to hire for it. You may have technical or cultural barriers to adopting a new language. Or it may be that you just love it. Your JavaScript project is growing likely larger and more complex. You probably have more of your code base moving into the realm of JavaScript than ever before. And you want to increase your confidence that it's correct. You want to check for usage bugs at build time and feel more safety at runtime. You want it to be readable. You want to understand developer intent more clearly and be able to respond appropriately. Both of these factors should help you improve refactoring and maintenance ability over time as well. You want to get feedback from a tool that can give you developer time help and enhance your code tooling, and as we'll see in a moment, the barrier to entry is low. So let's learn how to set up Flow in our projects so we can start to feel some of these improvements.

  8. Setup Your Project for Flow Intro to Flow in a Project When starting out with a new tool in your tool chain, it's important to see how it fits into your environment and workflow. Let's take a look at the tools that flow provides and figure out how to get Flow integrated into a project. Flow provides some simple, powerful tools that we're going to tryout together, so let's get ready for our first peek at Flowtype in a project.

  9. Install Flow-bin To start our project, first we'll make a new directory to test out the things we'll learn. Let's go into that directory. Once we're there, we'll set up our project as a node project by running npm init pass in the -y will bypass all of the question prompts and we'll get a default package JSON. Now we're ready to go get flow. We'll install a package called flow-bin. So what is flow-bin? Flow-bin is a module that contains binaries for each of the supported operating systems. These binaries will contain a CLI that will allow us to run Flow.

  10. Run the Flowtype CLI With Flow installed, we can try running it by typing Flow. Here we can see that Flow actually does not do anything. It says could not find a .flowconfig in the current directory or any of its parent directories. If by chance Flow does not return this message, you might not have the recently installed flow-bin in your path. You can fix that by adjusting your path to include the local node modules. Here if we export a path that appends ./node_modules/.bin, this will make sure that the local node modules are available in the terminal. Again, if we run Flow and we see that it cannot find a flowconfig, it won't be able to run. The presence of a flowconfig file will signal to the Flow CLI that this is a project that we want to run Flow on. So to create that flowconfig file, the Flow CLI includes this target, flow init, let's run that. And then, listed in our directory, we see that now we have a .flowconfig file. Running Flow again, it gives us a different result. We see here that a Flow server is launched. The Flow server will run in the background. The first time that you run Flow, this should happen. Later when you run Flow again, the Flow Server should already be running and you will get quicker results. Now we're going to make a directory called source where we're going to put the files that we want to run Flowtype on. First, we'll create a src/math.js file. Then let's open that up in a text editor. Here, we'll create a simple function that will allow us to demonstrate the affects of Flow on our project. We'll create a function that takes two numbers and returns the sum. Below this function definition, I will add a few usages that will show how this is used. Note here that I'm adding some probably unexpected types. Here is string a, and secondarily, a string b. Let's save that file and go back to the terminal where we can run Flow again. Still, we see that there are no errors. Well in part, that's because we haven't told Flow that we want to type check this particular file. Remember that Flow is a gradual typing system. So we mark the files one by one to say which we want Flow to operate upon. We do that by adding a comment at the top of the file that is the pragma that shows that Flow should be active here. Now if we run Flow again, still no errors. Well, what is up? Well, we haven't actually used Flow inside the file. We've said that it's active, but we haven't given any data type information here. So we're going to constrain our add function to only take two numbers. Now if we run Flow again, we get our first Flow error. Now this is interesting because there is quite a bit of data on the screen and I'll just walk you through what that error looks like. Here we see two errors, the first error you can see is from src/math.js on Line 7 and the error messages cannot call add with string a bound to a because string is incompatible with number. This is saying that the input data type on Line 7 cannot be bound to the parameter a on Line 2 because it is a string and that's incompatible with how we've defined our function. The next error looks similar, it's just showing that the other usage of the second parameter string b is also incompatible with type number. Well these should be pretty easy to fix. Usually after Flow gives you an error, you're back into your source code trying to make it match what your intent was and what Flow is helping you check. So here, if we remove those two bad usages, save the file, and run Flow again, we have no errors. Flow has helped us make our code correct.

  11. Attempted to Use Flowtyped Code Now that we have our piece of Flow-tested code, we want to see how we can use it within a JavaScript project. So I'll create a test HTML in the root of this project and we'll set up a little test harness that will show us how we can use this Flowtyped code. I'll create a small input form that will allow us to take two numbers a and b, press the add button, and show our result. Let's include the math.js file that we just created, and let's also include new code in app.js that will tie this together. Let's create that app.js code now. When someone clicks the add button, we should run this function go. Within go, we'll take the value of the a input field, do the same for the b input field, and pass the return value from the add function we just created into the result field. Now let's open that HTML in our browser and see what we have. Here is our simple form, we pass the numbers, we press add, but nothing happens. If we open the JavaScript console, we see an immediate error missing parentheses. Well how could that be. If we look at the source code for add, we see that it still has our Flowtype annotation in it. Flowtype annotations are not a part of JavaScript the language, so the browser does not know how to interpret our JavaScript. We need to make an adjustment.

  12. Configure Babel for Flow What we want to do is process our JavaScript to take out the Flowtype annotations. A perfect tool for this is one that JavaScripters should be familiar with and that is called Babel. Babel is a tool that allows you to take JavaScript and change it from one state to another through a type of pipeline. This pipeline of transformations are defined by plugins that you can add that change the content of your JavaScript. A preset in Babel is just a collection of plugins and here we want to install babel-cli to be able to run Babel and babel-preset-flow to have that collection of plugins that will be associated with transforming Flow into browser-ready JavaScript. Let's install both of those, then let's create a .babelrc file in the root of our project that will define the transformations we want Babel to make. Here we defined the preset we've installed Flow. You'll note that the babel-preset- is missing from the front of the name of the preset and this is implied. Now to run Babel, we run the CLI babel and then give it the file we want to process. Let's feed it our math.js file. If we run it through Babel, we see the output in standard out and it shows our file now without Flowtype annotations. This should be ready for the browser. Let's look at a couple other options that Babel gives us. If we send it that same file math.js, we can also give it an output file path, in this case, maybe math.browser. This is done with the -o flag. Now you can see we have a new file, math.browser, and it contains the transform JavaScript. There might be a better option still and this is using the -d flag. If we take our entire source directory and pass it with -d to a dist directory and run that, we can see that it takes everything that was in source and moves those as transformed files each into the dist directory. If we look at the content of dist, it indeed has both files. So let's go back to our test HTML, and instead of using source math and source applets, let's use the dist versions which have had Flowtype removed. And now when we go back to our app, we try add a few different permutations here, it's actually working. We have browser-ready JavaScript. It works at runtime and has been verified at build time.

  13. Flowtype Editor Support Flow on the command line is the most common and most reliable. It's a strong tool on the command line and it seems optimized for this. There are also editor plugins that you can install to get Flow help and feedback from inside your editor, but this is not the case that Flow seems optimized for and there is not a lot of support from Flow itself at times to support this. This means that getting Flow integrated into your environment will require some fiddling on your part based on the particulars of your environment. The tools that are available and the ways to integrate them seem to change often. These tools are created by enthusiasts and the different editors and there is rarely anything that seems very official. If you would like some pointers on what's available today and how it might be used for editor integration, please go visit gowiththeflowtype.com/editors. In my case, I've been using Space MAX here in these demos, and so I'll show you a little bit of the feedback that is possible within that editor. If we go back to our test project and open math.js and if I add back one of those bad use cases where a string is passed to a number, you can see here that the red underlining happens and I can open an error dialog and it will show me the same error message that we saw from the terminal CLI within the editor. If I fix that usage, I immediately see that the red line is gone, the error has been removed, and Flowtype has obviously been satisfied. Once we have our tooling in place, let's go use more of Flow in a new project.

  14. Start Your Project with Flow Types Introduction If you start a fresh project today and want to use Flowtypes in it, what would that process look like? Well, let's go through that process together. We're going to take some time to catalog some of the powerful types that Flow provides for us, and we'll see how it changes the developer experience for us as we set things up.

  15. Project Intro We're going to be making a choose your own adventure game. This is a story created by the Cub Scouts that I help lead which you may have already concluded given such a colorful title. It's a gripping narrative to be sure. By the end, you'll have a text-based game that will run in your browser. It's mostly a front-end web UI project. Pertinent code is just JavaScript. We're going to set up some tooling and it'll feel like a modern web app. We'll use some basic technologies that you should be familiar with, such as HTML and CSS, and there will be a couple of libraries that we will use as well that will require some context in introduction. These supporting elements are not the focus of our learning, they simply provide the real project context where we can use more of Flowtype. As we create the project, we're not going to go through the supported Flowtypes in an order that's necessarily simplest or most common to most complex. Our order will be what you might actually encounter when developing an app in real life. We'll end up covering a fairly comprehensive list of fundamental types supported by Flow.

  16. Start the Project with Flow First, we're going to make the directory for our project. We'll make it called game and change directory into it. This will be a project that takes advantage of node-like environments so we're going to start with npm init passing -y again, we'll bypass all the questions, and just create the package JSON. Since this is a project to demonstrate Flow, we're first going to install flow-bin to get the binaries to run the Flow CLI. Passing -D will save that to our dev dependencies and package JSON. And when we call flow init, again, we're generating that .flowconfig file. Other tooling we'll need includes Babel. We'll install the babel-preset for flow, the babel-preset for stage-2, which will allow us to do things such as use the spread operator, and then we'll start up our editor inside of our new game project. And we can see some of the files that we've generated. Let's create our .babelrc file. This will tell babel which of the presets that we have installed that we want to use in our pipeline for transforming our code. Now let's install another tool. This is parcel-bundler. This is a tool similar to webpack that will allow us to work in terms of modules that feel like node modules inside of our project and it will allow us to create a browser-ready code bundle that we can ship to the browser. Once that's installed, let's go to package JSON and create a start script that will use parcel bundler. The parcel command is available to us after the recent install. Serve is a command that Git understands that will create a web server to serve up the code that we're going to create, -p option allows us to choose the port 1337, and static HTML will describe the entry point, in this case, an index.html file in the static directory which we will make. So let's go ahead and make that file in the static directory. Our project will be created in JavaScript and most of the code, even the UI generation will be done there so this will simply be a landing place for that code. We'll create a div with id of app. This is where we will mount or render our application. We will create a script source tag for the index.js file, which we will make, and inside of that file, we anticipate that we will have some way of getting our app started. So we'll create an App.start method and then let's fill in some details in the head, again our app title, and then I'll link to a Style Sheet again that we will take care of shortly. In the demo materials for the course, there is a prebaked Style Sheet for this project. Since CSS is not the focus of what we're doing here and provides no opportunity for Flow usage, we're just going to copy that from the prebaked section of files. We'll copy it into static index.css. If you need these files, look in the demo download section of the project or go to gowiththeflowtype.com/demo. Now let's go create the index.js file. Index.html is our entry point, but index.js is where we're going to start writing our code. Just as a test here that integrates well with our index.html, let's write a snippet of code that will set the innerHTML on the app id element. Now if we go to our terminal, we can run the npm script that we set up earlier npm start. Here we see that parcel is active and it starts as a server on localhost 1337. Let's go there in our browser, and sure enough, we see cosmic powers, which is what we had written in our index.html. We're in business. Let's also check and make sure that we're healthy in terms of Flow. Now here we run into our first error and this is an interesting one because we haven't seen it before. If you look at the location of the error, it is in node modules. These are all things that we don't really care about. It's in node modules, which means it's someone else's code. We're not trying to flowtype that code. Now we will look at a feature of Flow that will allow us to control where we are doing our Flow checking. If we look at our .flowconfig that we created earlier, you can see several different options that have been created here as placeholders. In each of these sections, we can list different things to change the effects of Flow. The one we will look at now is the ignore a section. Here we can list files, directories, and patterns of those things that we can tell Flow to ignore the contents of. Here we will create an expression that tells Flow to ignore node_modules in any subdirectory and all the contents within it. If we go back to the terminal after making this change and run Flow, Flow will start a new server because of the change in flowconfig. We're in business.

  17. Create the First UI Now we will replace our test UI and start to create the real UI of our project using the tools we desire. We will install a tool called lit-html. Lit-html is a library that will allow us to use JavaScript to render our UIs, so let's change our index.js file. First, let's add the pragma for @flow indicating that we want to have Flowtype type check this file. Then let's import the two major functions from lit-HTML, both html function and the render function. Then we'll create our first UI function for our project called index. This function will take no parameters and will return the html function called with an HTML template and backticks, this is a string template, and we will simply output an h1 of a test string. And now to create the app start method that we expected to call from our index.html file, we'll attach it to window, and in start, we'll call lit-html's render function with a call of our index UI function, and we'll attach the output of index to the app element in our HTML. Let's switch to terminal and check Flow and we receive another error, Cannot resolve module lit-html. To solve this problem, we need to get some typings for this third-party module. We're going to use a tool called flow-typed and we'll install and save that. With that installed, now we can call flow-typed and search it for existing typings called lit-HTML. Since there are no typings for this project yet, we are going to call flow-typed again, but now we will create a stub called lit-html and this will put a new file inside of a flow-type directory in our project. If we look at that file, it's done a lot of things. It has created an any based typings file for what it sees as the public API for this module. Any is the most basic and most liberal type inside of Flowtype and it essentially tells Flow to ignore what this type is. It doesn't care. So this is inherently dangerous. It's okay for a stub to make Flow happy, but it's not providing us any type safety at all. You'll also note some different syntax inside of this file. This file is called the Libdef, or library definition file, and it is to describe the module lit-html. Now whenever we use lit-html globally through our project, we will be able to use these typings. This syntax is different than the typings we'll use inside of most of our app code and we'll learn more about libdefs later. For now, we can just trust that it will be happy with the lit-html module whenever we use it. Now when we run Flow again, we get the same error. Something is still wrong. We know we have created the libdef correctly, so something is wrong in our config and now it's time for us to learn another piece of the .flowconfig file, so let's open that, and let's go to this third section libs. Libs is where we list where we list where we want Flowtype to look for our libdef files. When we ran Flowtype's create stub, it created a Flow-typed subdirectory. That is where our new libdef stub is and that's where we need to tell Flowtype it is as well. So if we type that as a directory here, save it, and re-run our Flow command because we changed the config, we get a new server and now there are no errors.

  18. Complete the UI Even while using lit-html, we're using mostly a test UI. So let's get to the point where we can create the UI that we desire for our game. This is a text-based adventure game. The main bits for our UI as we imagined them will be a title, some sort of bar at the top, the main text of the book that we're reading at the moment, a prompt section that will allow us to choose what part of the new adventure we're going to choose, and then finally, the choices or the places that we can go to branch on our adventure and this is a screenshot of what that might look like in the final product. So let's go back to our index.js file, and instead of our test string, let's start to lay out these pieces as we've seen in this visual spec. First, we have an index page container, then we're going to create a title function, this will be a separate UI function that's called from within index, then for styling, we're going to create another container index_body, and remember that all of these class names are coming from the prebaked Style Sheet index.css. Inside of body, we'll create a text UI function and then let's create a footer. The footer is what's going to appear at the bottom of the UI. It's going to include a call to the prompt UI function and the choices UI function and this corresponds nicely to the pieces of our visual specification. Now we're going to do something interesting here and duplicate the footer. One of these footers, we're going to hide, the other we're going to put an additional class composed with an overlay modifier. The first hidden footer is to provide the exact vertical spacing at the bottom of the body that we need to create space so that we can always scroll to the bottom of the body of content. Here we can see a prebaked experience of what that might look like in the end. Now let's go create each of these UI functions. They're all going to return lit-html's HTML function, we'll change the names, and we'll start filling in the basic HTML. We're filling in all portions of this project so you can have a feeling of how it feels to start it, but if you'd rather have the prebaked version for you to start from, again, look in the demo files in the after section and you will see this already made. The title will contain the title of our story. Onto the text function, this will contain the main text, the narrative of the story. Now to the prompt function, this will essentially ask the reader where he wants to read from here, what choice will he make. And then finally, in the choices section, we will have the choices listed for the next step in the adventure. Now we've created stubs for all of those functions. We'll make sure that our server is started and we'll refresh our browser, and now we see all bits of the UI using the prebaked styles and our stub content.

  19. Design the Data Model Now as we start thinking about putting real data onto these UI functions, we need to start thinking about the shape of our data. This shape will be defined in a type. Let's think of our data in terms of narratives. At each step along the path of our adventure, we encounter a narrative. So here, we'll set up an object. A narrative might have several things associated with it. To describe that in a type, we'll go to the narrative variable name, and after, write a colon. After the colon, we'll have two new curly braces, and here we will define an object type. The curly braces indicate the object. The colon after the variable name indicates that what follows will be the type. So here we will write a literal object type that describes what fields should be inside of this narrative. So what fields should be inside a narrative? Well first, and perhaps most obvious, might be the text. What is the text that I am reading in my book? And for that field, it also has a type and it is of type string. Now that we've defined the field in the object, we can write the actual type inside of the value for the narrative variable and here we write text and get it some placeholder. What else is in a narrative object type? Well it has a prompt field. Prompt would also be a string and here we write a value for that. But let's think, will every narrative that we encounter along our journey have a prompt? Not necessarily. What about the last step in the journey? Instead of a prompt, there would be a The End. There is no next step to take. So we're going to add an extra symbol here to the end of prompt inside of our object type and it will be a question mark. Prompt? now describes a maybe prompt and this is a maybe type. This means that prompt as a key on this narrative object type may not exist at all. What else is in this object type? Well, you probably guessed it. Choices. We want these to exist as well. And how are we going to model our choices? Here, we're going to assume that choices are in a somewhat normalized form and we're going to list numbers for choices. These numbers are going to correspond with a narrative id. So when we've linked to another step in the adventure, that's a link to another narrative. We're going to link to that narrative's number or id. And then after number are the square brackets, which means there could be multiple choices in an array. So this is an array type, an array of numbers. Now if narratives are identified by a number, we need that on this particular narrative object type as well. So let's add id as number as an extra field. Let's put in some fake data here and show that there can be choices of 2 or 3 coming from the first narrative. Now if we're going to have three narratives, let's create some other variables that will show that. We have n1, n2, and n3 for our first 3 narratives and we can set up these with appropriate ids and placeholder text. But here we have a problem. We want n2 and n3 to be narratives just like n1 is typed as a narrative. But n1's type is an object type in lined with the declaration of n1. What we want instead is a narrative type, something that can be defined as a type across all three of these narrative variables. So let's type capital Narrative to represent this object type and we'll replace what is this object type with narrative here as well and move that object type above to this type alias. Now this becomes a type alias when we use the keyword type and we specify this name narrative as the alias for this object type, and now we can use that type in multiple places. Except for one-off types, this is likely the way that you will create most of your types because most types will be reused in your applications. As we're thinking about what we want as fields on n2 and n3, we're going to make another tweak here to the narrative type that changes choices to a maybe key as well. There's another part of choices that we're going to need and that is what we're going to call the choiceText. The choiceText is going to be the short string that appears in the choices section of our UI that is an answer to the prompt. So if the prompt is how will the show go on, the choiceText will be the answer to that prompt. And just as a prompt is potentially missing, we're going to make choiceText a maybe type as well. For our three narrative demo now, n2 and n3, we will fill in the choiceText because these are linked to from n1. Notice that we're doing a fair bit of type work and data structure work early on in this data discussion in our project before we've even used it inside of UI functions. Here, I want to quickly demonstrate an alternate syntax for the array type. Here we can use capital A Array and pass next to it in angle brackets the number type. This is a way of defining that exact same thing in a different syntax. This is called a generic type. Generic types are parameterized meaning that you have to pass in a type when the type is used. This is a method of polymorphism for our types. We changed the type at the usage time. Generics are used when it's hard or inconvenient to nail down the type and this is the case with arrays. How many array types are there? We can put so many different things in arrays and here we define that we want numbers in our arrays, but I prefer the previous syntax because of its brevity, so we'll put that back. We've completed our narrative object type for now.

  20. Layer Data onto the UI Now that we have our UI functions in place and a solid object type for our narratives, it's time for us to layer real data on top of this UI. If we go to our index function, we're going to start a pattern where each one of these functions receives what is called props. This is a term borrowed from React and we will use a similar convention. Props is simply an object or bag of data that we can pass to each one of these UI functions, and while we're defining that each one of these functions takes props, let's type props as well. Here, we're creating a function type inside of the parameter list where props is passed, we're saying that that parameter of props will have a type of IndexProps. Since this is a named typed, you can see that now we are going to have to create the type alias for index props. And if we think of what we might want in our props, for one, we want the narrative, the one that we just created so we're going to create a field called narrative that is of type narrative. There will be a new field called store and store will have an object type. Inside of this object, there will be a field called data and data will be of type Narrative array. This way, we will have a narrative that is active and a store which will contain all possible narratives that will allow us to link to other narratives. Since now we've defined that index must take the indexprops type, we want to go back to our call to index, that's the entry point to this app, and make Flowtype happy. Here we can see that the editor is already underlined in red indicating that there is some problem. So let's pass in the first narrative and create a new store with all possible narratives inside. Let's go to our terminal, create a new tab and run Flow there just to make sure that we're all good, and so we are. And if we go back to the browser and refresh, what do we see? Well, we don't see our new data and this is because each of our UI functions, though it now receives props, does nothing with them. So let's go through each and actually use the props that are passed in. First to the text function. Now because this is a string template, we will used $ curlies to provide JavaScript data within the template. So on props, we've defined that there's a narrative field, and on the narrative, there is a text field. And if we go to the browser, refresh, we actually see the change with our dynamic data. Let's do the same for prompt and pass narrative.prompt. Now here, on choices, we have a little bit more to do. Let's extract one of these choices into a new choice function. We'll type our choice function as the others to receive props and it returns the HTML of the narrative choice text. Now what narrative should be here as choices? Well, we're going to have to find those choices. Once we have the choices, we'll map over them returning the choice UI, but what does getChoices look like? Well if we pass in props again just to keep it simple, let's go to the store.data and filter where the narrative is found inside of the current narratives choices. We're going to match on ids. If we refresh the browser, we actually don't see a change. If we call Flow, we see a new Flow error, cannot call props.narrative.choices.some because the property some is missing in undefined. Well, yes it is. We expect that some would be a method found on the array and choices should be an array, but if we think about to our object type for narrative, choices is actually a maybe type. That key might not exist and Flow is telling us that, that we have not handled that null case. So if we go back to getChoices, we're going to have to do a check. Is narrative.choices an array? If it is, filter on that other condition that we previously made. And now, there are no errors in Flow. Flow has helped us make our code safer. And yet, in the browser, we still see no change. Well that could be because we're not even using the dynamic text. Let's use that and now we do see an option a and an option b. Fantastic. We've now taken our data and overlaid it on top of our UI. We've done this with fake data, but we can be confident that our data, how it's typed, is interacting well with our UI and what it expects. Let's go to the next step and get a little bit more sophisticated.

  21. Fetch Data We're going to fetch our data asynchronously instead of defining it inline. Here, we're going to set a new data variable to the value awaited by fetchAll. We're going to use await as in async await to handle the async call that fetchAll will be. FetchAll is a function that we have yet to make. And then when that data comes back, instead of using n1 through n3 as stated here, we'll set the data store to just the value of data. Now let's extract n1 through n3 and put it in a different place for data fetching. Let's import fetchAll from narratives/data JS File, which we will make now. Inside of this file, we'll have n1 through n3 stub data again, but it will be returned by the fetchAll async function in this array. So let's do some refactors to get that in the array, but we don't want to lose our typing information, so let's add the flow pragma to the top of this file and start doing some type checking. We see a couple problems right off. These might not have been ones we expected, Cannot import fetchAll because there is no fetchAll export in this new data JS file, and so there is not. Flowtype has caught a simple error, which is we have not made that exported, so let's do that now. If we run Flow again, we've gotten rid of that error, but we have one remaining, cannot resolve name n1. Again, this is a simple error and back in our index.js file, we've removed n1 as a variable declared here, but we're still using it. What will we use instead? For now, let's just use the first narrative returned in data. Note here as well that data is not typed as a narrative array, but it is inferred because the index props type, which does still exist, is okay with us getting the 0 with element of data and passing it as the narrative field value. Let's go back to data and we want this to be typed as well. Inference is fine, but this is good for us as well for documentation. So what will fetchAll return? Well, it's an async function, so it's going to return a promise, but a promise of what, and here we see again a generic type. Promise is a type that could return any number of things, and so we are going to pass in what that thing is and we know that this is going to return an array of narratives, but here we don't have the type narrative. Instead of duplicating it, we're going to go import it and here we see our first example of module types. When we import with the extra keyword of type, we're importing a type, not a module of code. So we're going to import the type narrative from a new file called types that will live in the narrative subdirectory. Let's go back to index.js and extract that narrative type and create that type's JS File. We'll export the type so that we can import it elsewhere. While we're here, let's tweak the narrative type just a little bit more. We see here that id is of type number, which is true and will continue to be true, but I think we can provide a little bit more meaning. So let's create a new type alias. This alias is called narrative id and is of type number. So the type will remain the same, but now we will use narrative id, which gives us a little bit more information regarding our intent. This will show that the number array is actually an id array and that it matches the id field in type. We're documenting our intent. Now back in our index.js file, we have to do what we did in the data file and import the narrative type. If we go to the terminal after this refactor and run Flow, there are no errors. Flow has helped us make sure that our refactor is solid.

  22. Setup Routes Currently, our app allows us to see one narrative at a time, but there is no navigation. Let's change that so that we can move between narratives so that when I click on a choice, it takes me to that narrative that I have chosen. For this, we're going to need another tool and we'll go to the terminal and install a module called page. Page is a client-side router. Now we want libdefs to go with our third-party libraries, but if we search Flow-typed, there is not a libdef for page. So again, we will create a stub for that. To accommodate routing, we have a few refactors to make. One is that we will move our index.js into a Pages folder. So now we have pagesindex.js. And now in the place of index.js, source index.js will now be a call to map our routes. Now let's create the routes.js file. Remember to keep adding those Flow pragmas. In routes.js, we will use the page router. First, we want to export that map function that we were calling from index. And within map, we're going to define each of the routes that we want in our app. The page function takes as the first parameter the path pattern and the second will be the handler function or the code that is run when this path is activated and what is the handler we want to call? Well, let's import our only UI to date and that is the index page from pages index and we will use that as the handler, and when I go to any id, meaning a narrative id, /id will go to index, and when we come to the app@/, we're going to want to start on the first narrative, so we'll redirect to there. And then finally, to activate page after defining all our routes, we call page with no parameters. Now let's run Flow and see how we're doing. We see here a Flowtype error inside of the routes.js file that we've just created. Missing type annotation for underscore. Here Flow is saying I cannot infer what this function type is so you're going to have to tell me. Well underscore used here as a convention to say I don't care about the arguments to this function, if any. But Flow doesn't understand that convention, so instead, I'll give it a convention it does know, which is passing empty params, which is JavaScript is an empty parameter list. Now let's go back and run Flow again. We still have two Flowtype errors, cannot resolve module in both cases. Why could this be? We just moved what previously was our index.js file into the pages subdirectory. This seems to suggest that these paths did not get adjusted, and therefore, the modules are unresolvable. Let's go to the pages index.js file and take care of those paths. Now we must go up a directory to find them. Running Flow again, it seems to be happy. Now let's refresh the browser and see if any of this has taken. When we click choices, still nothing happens. Awe, there is still a piece of the puzzle that we have forgotten to do and that is the route handler itself, which we were importing into the routes file, but we haven't actually written. So let's create a default export for our index page. And what happens here should be what used to happen in the app start method before. And so now since we have routing, we don't need app start because our app will start as soon as index runs and the routes are mapped. Let's restart our server just to make sure that we have brand new HTML. If we refresh our browser, now we can see that we've been redirected to /1, so we're on the first narrative, but as we hover over each of the choices, we see that these actually don't link anywhere yet. So let's go to that choice method and add an href where we haven't had one previously and this should go to /narrative.id and let's make the title go back to the beginning of our story. Now when we hover, we see the ids, and when we click them, we see the URL change, but one thing we don't see change is the actual data or story on the page. Why could that be? That could be because this route handler is not actually receiving any information about what route we're on. So let's create a new parameter called context and give it the type of RouteContext. Let's set up that type alias here. It will be an object type that has a parameters field. Parameters will itself be an object type with a special field. This field has its key surrounded in square brackets. This is called an indexer property. This means that the params object can have any number of keys that matches the type string, but they can be of any name. We're not naming those fields here and this makes sense because a route might have any number of different named parameters. Each one of those parameters will be of type string, which is this second value type here. Now that we've defined the parameters type and passed it in as context, let's use that context to change what happens in our route handler. We can go to the store and find the narrative whose id matches the id on the route. Once we find that narrative, we'll pass it into the index. Now if we go to our terminal and run Flow, we have a problem, Cannot call index with object literal bound to properties because undefined is incompatible with narrative. Now let's follow these footnote numbers here. This means that the object on props expects a narrative, but find will potentially return an undefined and this is incompatible so we can't call index with an undefined narrative. Well, then we must provide some extra checks in our code to say well only if we find a narrative will we call render. Otherwise, for now, let's console.log something helpful. If we run Flow again, there is no error, and now if we run the app in the browser, we actually see nothing at all. If we open the console, we see our helpful error message. Lame. So what's going on? Why can't it find the narrative? Well our finding is based on ids, so let's try to tighten down this code a little bit more. We'll go up to where choices is found, we'll say that those need to be found by ids, NarrativeId, and we'll need to import that type because we don't have it yet, and when we try to import it, it's unavailable because we haven't exported it, so let's go fix that. If we run Flow, it's happy. But when we run it in the browser, nothing has changed. What else could we tighten down here. Well it's not really the choices that are the problem, but the main narrative, so where are we finding that? It's here in the route handler. So let's define a few more types to try to get to the root of the problem. We know that the parameter id is going to be the route id, so let's extract that here to routeId. And then let's add a type annotation of number. We expect that to be a number just like all of our narrativeIds. Now when we run Flow, we've exposed an issue, cannot assign the ctx.params.id to routeId because string is incompatible with number. Hmm. We have said that our route context parameters are all of type string as they are, but we've also said that our narrative ids are numbers. We can't implicitly change that type, so we must parse it and change it explicitly. Then we have a number produced from a string. Running Flow again, there are no errors and now we have numbers comparing against numbers when we do our triple equals undefined. Refreshing the browser, we actually have a narrative found. Even better when we navigate to a choice, we see that navigation work and the app changed so our routes are working as well. Fantastic. We've created the basis for our book. We've started the project from scratch, we've used Flow in that entire process and we've seen how the types will help us drive the initial development, work through a number of problems, and we've been exposed to some of the most basic and useful types available in Flowtype. From here, we'll experiment with more Flowtypes as we expand this project and we see what it's like to use types midstream through a project in refactorings and other changes.

  23. Add Flow Types to Your Existing Project Introduction Most of us have other JavaScript code bases with no additional type information from Flow or otherwise. How does picking up development in the middle of one of these cycles feel? Let's go in after the fact and add some types to the project midstream. Let's see what additional information that types can give us in these situations.

  24. Project Next Steps Now we're going to skip ahead to a later stage in this project. I've prebaked some changes into the code base that will allow us to experience a little bit of what it's like to Flowtype and code a project midstream. Here's a short walkthrough of what's been updated behind the scenes. To get a copy of the code at this stage, see the project demo files or go to gowiththeflowtype.com/demo. If we list the contents of the directory, we see the usual suspects, so we'll jump right in with our favorite editor and get started. Our first stop is the package JSON file. In here, we can see a couple changes from last time. The start script is now a combination of two scripts start:client and start:server. They're run in parallel using npm run all. Start client is well known to us and it is the parcel script that is serving our client bundle at static/index.html. The start server script is a new script using json-server to run a fake REST service at port 1338. The data for this service is found in DB JS, which we will see shortly. There are also test scripts here that suggest that there are test files that have been added and processes to run them. Test code shows us that this is run by jest, a popular JavaScript test runner. Let's have a look at our new json-server. This has a single export that returns a function returning an object with a key narratives in it. Here, narratives describes the resource that this REST API will return. If we look at the routes JSON file in the server directory as well, we see a small amount of config that will allow API v1 to be prefixed in front of our server paths. To see the server in action, we'll go to our terminal and run npm run start server. Now that data should be available and served at localhost:1338/narratives. It would also be available at APIv1 narratives. Taking a look at our data.js file, it has been changed as well. Instead of returning a static list of three narratives as before, there is a fetch conducted by the node-fetch third-party library that is going to our json-server 1338 at API/v1/narratives. This JSON is then parsed and returned. That much is tested in a new spec file that accompanies data at dataspec.js. It verifies the fetch, the parse of the return JSON, the returning of those results, and a couple error conditions. Moving onto pages index, we see a couple new UI functions in use. Here are the image function and the end function. Both of these are new UI functions. There is now an additional spec file here as well that will exercise these various bits of UI. And finally, to the routesjs function. There are a few new things going on here. We have moved RouteContext here, we have a new Router type, which is mostly a placeholder at this point, and a NextFunction type that describes the next function that is called at the end of route middlewares. Next, there is a routeWithPage and route function that are serving as wrappers around the pagejs third-party library. This shows how multiple middleware can be applied. As with the other new files, this also has a new spec file that exercises a couple elements of the router functions. To see it all together, we'll run npm start. Opening our browser to 1337, we see the app as before and it works as expected. Opening a second tab, we can exercise our tests. In the watch mode, tests will re-run as files change. Re-running all the tests, we see that we are green and in yet another tab, we can run Flow. Type checking our code and we see that there are currently no errors.

  25. Extract Data Store Class Let's get in and continue improving the quality of this code base. First, we're going to look at our narrative store. Up until now, we've been data fetching and storing directly in the index page handler. And a move towards single responsibilities, let's extract a class for storing narratives first. We'll begin with creating a narrative's specs store.spec file. Here we'll show that we can also Flowtype our specs file. From here, we will use the describe, test, and expect keywords that are common to test runners to assert certain things about this new store. Our store should have a way to find narratives and return them based on a unique identifier. We'll create a new store instance and instantiate it with a narratives parameter. This narratives fixture is simply a list of narratives that can be passed as starting data to a store. For this particular test, we'll expect that the third narrative will be the one we're finding. And so, we will call store.find with that id. We will assert that the narrative returned is the one that's expected. We'll make sure to import the new narrative store from a yet to be created store file, and now we'll go create that file. We'll be sure to type check this file and create a named export of NarrativeStore. This will use the class keyword to show that we can instantiate this data structure. The find method will take a NarrativeId. And if we find the specified narrative, we will return it, but there is a possibility we won't find it and so this will be a maybe narrative type. Let's import the type narrative for use here. To implement, we will assume a collection of data that is an array where we can use the find method to find the narrative whose id matches the one specified in this method, and where does data come from? So far in our test, we have used the constructor to pass in the initial data, and so we will hear the narratives parameter will be a maybe array of narratives. Note the parentheses used around narrative array where our question mark is on the outside. Saving that file, let's run Flow to type check our code. We have two errors. First, cannot assign narratives to this.data because property data is missing, and second, cannot get this.data because property data is missing from NarrativeStore. Both errors are very similar and will be solved in the same way. This will require us to do something not normal in untyped JavaScript files. That is, if we have a field in our class, we need to type it at the top-level. And so, we'll add the property data: Narrative array. Saving and re-running flow has removed the error.

  26. Code to Interface Now we have a NarrativeStore and we're seeing a potential pattern that can emerge. There is probably a common interface here. We see a data structure that has a collection of data and a way to search for and find singular pieces of data in that collection. This has the makings of a generic store interface. So let's create a new type Store in the common/types file. As usual, we'll flow-type the file and we'll export our new store type. We will use a new keyword interface to define this type. An interface is different than other types in Flow. Interfaces are compared structurally when they are type checked. Other structures such as classes are compared nominally. The difference is that structurally typing matches on fields or shapes of the data. Nominal typing or named typing matches on names of types. For our store, we want it to be a generic interface so that it might represent many different kinds of storable things, so we will give it a generic type T. The store will have a data property with an array of that type. To find something in that array, T must have a unique identifier of type number that can be passed in. The return type is a maybe of T. To use the interface on our narrative store, we use the implements keyword. Now, narrative store must structurally match what we have said a store interface is and so it does with a data field and a find method. If we run Flow, we see that it agrees with no errors. Back in pages index, now we will replace store with a new instance of our NarrativeStore passing in the fetched data. To use it, we'll import the narrative store from narratives/store.js. Saving the file, we can see the store working as desired.

  27. Extract Middleware Function Back in pages index, we still observe multiple responsibilities contained here. Data fetching could be extracted to a specific middleware to provide data based on the route taken in our application. Go into the routes.js file, we could envision this as a fetchNarratives function that is run before the index middleware is reached passing on the data that it fetches. Let's work towards that solution. As a first step, let's create a new middleware spec. We'll flow-type our spec file and now describe what is in this fetchNarratives function. For this function to be useful, the data that is fetched needs to be added to the route context so it can be passed down to the index middleware. The data that is expected is an array of narratives. The route context includes expected things, such as params. And now, it will have a new field called store, which will be an instance of the NarrativeStore. For now, the router will simply be a placeholder, and because every middleware needs to call a next function to pass onto the next middleware in the route, you will create a nextSpy. Spy because we care about the state of context at the time that next is called and we expect that the context will have a store filled with data that matches the fetched expected data. Once next is called, our test will be done so we will create an async test. Finally, to call our subject under test, we will run fetchNarratives passing these parameters that we have created. There is an additional parameter that we need to pass as well and this is a dataStub. We need to stub our data fetching because we don't want our unit tests integrating with an external JSON server. So instead, we will create a dataStub that has a fetchAll function that returns the expected array of narratives. Because we're changing what fetchNarratives needs, we're going to change the name of fetchNarratives to fetchNarrativesWithData. This will describe the usage of the function that we are using in test. Under normal circumstances, we don't want our clients to have to pass a data implementation and we'll see how that works in a moment. For now, let's import fetchNarrativesWithData from middleware and the NarrativeStore in order to use them. And now to create the middleware. We'll create a named export of fetchNarrativesWithData for use in our test. This will be an async function for data fetching. The first parameter is called data. It has an object type with a field of fetchAll that should be a function that returns a Promise of Narratives. Second is a router. Third is the context and we'll leave off its type for a moment. Finally, is next or the function that is called at the end of the middleware. If we open up the routes.js file alongside, we see where these types are coming from and we'll import types nextFunction and Router. Normally, context would be a RouteContext as we have that specified in routes.js as a type. So we will create a new more specific type called IndexRouteContext for this route. It will allow us to add a new field to the RouteContext. We will also create it as an intersection type with the general RouteContext, so it will have all of the fields of RouteContext as well. The new field that we will add, in this case, is store, which will be of type interface Store of Narratives. Here, we can use the interface type because we do not need the concrete narratives store. Always code to the interface if you can. And let's import those types. Now if we fix up our function syntax, we'll be ready for the next step. Let's open up our middleware spec alongside so we can see what those steps are. In the end, we know that next must be called. Before that, we know that we must add a new store field to the context. Here, we will describe it as a new instance of NarrativeStore and what do we initialize the store with? We will fetch the narratives from our data fetchAll function. In test, data is passed in as a data stub, but for normal clients, they will use the data.js implementation and should not be expected to pass it in at all. Saving the file, we can go run Flow and check our latest progress. We have one error, cannot resolve name RouteContext. This should be easily remedied by importing that type. Running Flow again, the error is removed. Going to the routes.js file, we'll use fetch narratives as we envision it. Notice that we don't want to pass a data implementation here, and so we won't. Instead, we'll import this new fetchNarratives function reference. And then back in middleware.js, we must define it and we define it as fetchNarrativesWithData prebound with the data.js implementation. Checking Flow, we see no errors, and going back to the browser, we see it working as we expect, but there could be an easy reason for this and that is because we are not actually using the store or data fetching from the route middleware, instead where you're using it locally still. So let's make the adjustments to use the store from the context, find the narrative, and pass it and the store from context to the index UI function. Since context should have a store, we also need to adjust the type for RouteContext to IndexRouteContext where the store is known to exist. Then let's import IndexRouteContext. Flow checking, we have an error, cannot import the type IndexRouteContext as value. Use import type instead. We have made an obvious, but normal error in using import instead of import type when importing a type. Changing it to import type and we remove the error. One more check in the browser and all data is flowing as we expect.

  28. Handle Potential Errors with Types There is another improvement we could make to this code. It'll make our apps control Flow logic better and will expose us to some interesting types. If we think of the conditions that a data fetch might leave us with, there is essentially two results, either a success or error condition. Let's model these now. Currently, any error will result in a thrown error. So what if we surround this code in a try catch, we can return something in the case of an error and something in the case of a success, but what is that thing? To model it, let's create a new type. Let's call that type a result, something that might be a success or a failure. For a success, we'll call it an Ok. For an error, we'll call it an Err. And so, a result can be either Ok or Err and we'll model that as a union type. A union means either/or. A result can be an Ok or Err. Both ok and error will be a tuple type. A tuple is a lightweight data structure that can handle multiple unnamed fields. In JavaScript, it is implemented as an array with square brackets. The fields are accessed based on order using an array index. The tuple can be of mixed types and the arity or length of the tuple is fixed. In our tuple, the first index is going to represent whether the operation was successful or not. In an ok, the operation is successful true. In an Err, the operation is successful is false. Accompanying the error in the second index is going to be a capital E JavaScript Error, but in the case of an Ok or a successful operation, the type is unknown, it could be any type or a generic type. We will use the conventional capital T. To make T available, we must pass it as a generic type into Ok, and because Ok is a part of the result union type, we must pass T into result as well. Now we have a new type that we can use to adjust our expected tests. Now actual as it's returned is going to be a result type, a tuple, and we are going to test both parts of that tuple. The 0 index that the operation is true success, and the second index to verify that the return value is as expected and we'll adjust these other tests as well. Checking the test runner, there are some test errors and that's expected as we've changed the test, but not our implementation. If we go to our data implementation, and in the success case return true, the result of the narratives request, and in the err case, return false and the err that is caught, we have a successful and safe refactor. Let's continue this refactor in the next step in our middleware spec. Here, we expect that what comes back from dataStub fetchAll is now not just a raw expected, but a result type of true and the expected value. Now to change our code to correspond to the test change, we will return a result. If the result is true, it means we have successfully completed the operation and we will take the narratives and add them to the store. In the err case, we will log a helpful console message. So where do the narratives come from now? They come from inside the result and they are the second element there in the tuple. Adjusting the implementation, we can see the tests again and they are passing. But when we run Flow, we do not have such a rosy picture. Let's pull up our middleware spec alongside and examine both. Here we see that our dataStub has returned a result type tuple, but in the implementation, we are saying that the data parameter will have a fetchAll function that returns a promise of Narrative array. We need to adjust it to return a promise of result of Narrative array. Flow checking that, we have a new error, cannot resolve name Result. That's easily remedied. Let's import the result type. Running Flow again, we are back from our complicated error, Cannot call Narrative Store with narratives bound to narratives because error is incompatible with array type. Somehow Flow is not sensing that on Line 21 we are ensuring the success route of our tuple. And so, when we pull narratives from result on the next line, it's saying that it might be an error. We know that it's not an error because we are conditionally checking on Line 21 that Flow is still not pleased. In cases where the Flow type checker gives you an error that's seemingly impassible, you can always typecast to the any type. Typecasting allows you to tell the type checker that you are going to manually convert a type to a different type and typecasting through any is a very special typecast. This is because Flow will prevent you from usually making typecast errors it can detect, thus it won't let you cast to arbitrary types or even more specific ones. Normally, you can only typecast to more general types, but if you typecast to any, it's an intermediate wildcard type that will allow you to convert any type on the other end, in this case, a narrative array. Use this with care. And running Flow, happily our error is gone.

  29. Refactor Result Type But in fact, we think that we can do better than this tuple type in casting the successful Ok result of the narrative array. Let's reform our result type as an object type. Here, there will be an Ok field to represent our true/false literal upon which we can differentiate ok or error conditions. For the Ok type, the Ok field is true and the value is still of type T. For the error type, the ok field is false and the error field now will have the Capital E JavaScript Error. Now to change our middleware spec to expect this new result shape. And after this spec, the middleware implementation where now instead of checking tuples, we will check the ok field and then get the value field, avoid the any cast and pass it into the store. Running in our test, they are all green. Now to change our data layer. In the data spec, instead of using the tuple syntax, we will use the new result object type syntax. The test will be read, and now to fix the implementation. The tests are green and our result type refactor is complete.

  30. Create Error Page Let's round out some of the application error experience by creating an error page for cases where the app runs into difficulty. For instance, in a star route, we are catching all non-specified routes in an error handler. Let's import that yet to be created error page and create the pages error.js file. We'll Flowtype the file and export a default handler. For now, we'll ignore the first parameter and the second will be the normal RouteContext. To use this type, we'll import it and we'll import the other known bits from lit-html that we will need for our UI functions. Lit-html will allow us to render the results of the error function onto the app id element in the document. Our error function will return a lit-html template. And inside of it, we will establish the markup. This markup will use the CSS selectors that are predefined in the index.css Style Sheet. Because this is the error page, we will inform the user of the error, and because this is used in the star route, we will indicate that this is a 404 not found error. Finally, we'll create a link that will allow the user to go back to the beginning of the known story. Testing this on an unknown route, we have a build error. If this happens to you, just restart parcel and it will pick up the new files. Aww, our new error page. Our start route is activated when an unknown path is used such as /asdf/asdf, but if we use an unknown narrative id such as 1, 2, 3, we don't get our 404 page at all. Instead, we get a console log of lame. Indeed. And where does that come from? It comes from our pages index handler. Now that we have an error page, we want to connect it and use it in those places where up until now we haven't done much in the case of errors. What we really want to have happen here is when the narrative is not found, we want to redirect to the not found page. To redirect, we need a router and that is what this first parameter is meant to be. So now we will specify it more clearly and use it here below. Refreshing the page, however, we see an in-browser error. Router redirect is not a function. Hmm, what have we done wrong? If we flow type, we have no errors so it should be a function if that's what we're expecting. This handler is called from the routes.js file. Let's go there and see if we can sleuth around and find something amiss. Well, first things first, we see that Flow could be happy simply because Flowtype is not checked in this file. Let's add that Flowtype pragma to the top of the file and what happens when we run Flow now? Aww, errors are revealing themselves. This last error is particularly interesting. It's saying that a type annotation is missing for the REST parameter middleware. Index is one of those middleware. If we type it, perhaps we will expose more of the error that we are seeking. Back to routes.js, we know that middleware here as a variable length argument, will be an array of 0 or more functions. Those functions should take as their first parameter router, then route context, then the next function. Because middleware can return multiple types, we will have it return mixed type, which is a new special type. Usually, only a single type is returned, but where multiple types need returned, you can use the term mixed. Saving the file and rerunning Flow, we have a new error. This last error is related to the line that we just changed. Cannot call m.bind with null bound to the first parameter because string is incompatible with Router. We have defined middleware here as taking as its first parameter a router, but it's detecting that what we're actually passing is string. And if we look to the next line, we see where that first parameter is passed in the bind as path. Path is actually a string and here we have uncovered our error that's be lurking here for some time until types have been introduced. What we meant to pass is page as our router implementation. Running this again, our error is gone. We still have a few to deal with, however. Cannot call router.redirect because property redirect is missing from Router. Aww, this is a slight differentiation from the last time. Now it recognizes that it's a router, but it's saying that the redirect function is not present and that's because up until now router has mostly been a placeholder. Now we will add a property redirect, which will be a function that takes a string and returns nothing here represented as void. Running Flow again, we have different errors still. Cannot call fetchNarrativesWithData with router bound to router because property redirect is missing in object literal but exists in Router. Router, the type, does have a redirect, but the router that we're passing as an object literal does not. Let's go fix our spec file, and here in our router implementation, we will define a placeholder redirect. Running Flow again, we are down to one error, cannot assign new NarrativeStore to ctx.store because property store is missing in RouteContext and so it is. If we go to our middleware JS File, we can see here that context is the general RouteContext type, but what we know is that we want to add store to our context in this middleware. Changing it to IndexRouteContext should do the trick. When we run Flow, we have no errors. Now to make the redirect path work, we need to register a new route. We'll go to routes.js and add this /error/ error code route to go to the errors page. Now when we use the error 404 route, it will actually route to the errors page. Awesome.

  31. Handle Maybes Let's use the error redirect in one more place where we're simply doing a console log. In our middleware, if the results of data fetching and storing does not work, we want to say that there has been an error. For this error, we'll redirect to the error page, but with a 500-error code. To display the error code, we're going to have to adjust the page's error file. Now instead of a static 404, we will parameterize the code. The code will come from the ctx.params in the route and we'll default to a 404 if none is given. Those will pass to the UI function as props and we will define a new object type for that function. The props will contain a single field called code and we'll use that code in the template. Now if we route to error 500, we see the 500 displayed in the UI. Now that we found a couple issues inside of our router and we've used it for a couple new routes, let's go back to our router spec and see if there's anything that might be removed. This first test definitely seems like it's not doing very much. We're only testing that there are certain parameters that are passed into the function. What's more is, we can't really test in normal JavaScript what those functions are in type or name. So here, we're only testing length. What's made worse is that the variable arguments is throwing off the number, so we have a comment here. Not only is it a worthless test, it's somewhat misleading. This is a great example of where typing can replace certain types of tests, especially when testing the shape of things or APIs. To check the health of everything, let's re-run Flow, we have no errors, let's look at our tests, and they are all green. We have come to a good spot in our application. If we try out our application at this point, we can see the whole choose your own adventure experience and we've bolstered our confidence in the application at this stage. Through the process, we've used a wide variety of fundamental types and concepts available in Flow. Next, we explore further the concept of libdefs as an important piece of real world Flowtype projects.

  32. Use Flow with Packages Introduction Of course, it's not always our own packages that we need typed information for. These days, many projects have large components of third-party dependencies that need to be integrated with. Can Flowtype help here? Let's see what tooling exists for this and how the community has decided to help us out. Perhaps we can contribute back ourselves. We're about to round out our understanding of the fundamentals of Flow.

  33. Libdefs so Far We've seen some little pieces of what have been called libdefs, or library definitions, earlier in the project. These library definitions have been for typing third-party code that we did not personally write in this project. These libdefs are stored in the Flow-typed directory of our project. A libdef is a typings file that's going to help us to know how to use the API of this third-party code. Then we will be able to use it more correctly and with greater type safety.

  34. Flow-typed Community To get these typing files installed locally, thus far we have used the Flow-typed CLI. The Flow-typed CLI tool is connected to the Flow-typed repository of types that exists on GitHub. This is a project meant to gather and provide a quality set of common third-party typings that can be used by you and me as we write code that uses these libraries. It is a community driven effort that needs library maintainers and other contributors to create typings for other Flowtype users. Here on the site, there are guidelines for how to contribute and send in your typings files. Here we can see that there is a conventional directory structure for how we need to submit these definitions. This includes directories for each Flow version that we have tested our types under and it also includes tests that will allow us to verify the correctness of our typings. As we look at the contents of this repository, we see that there are definitions for npm style modules and here they are named according to the libraries that they represent. You may see some that are familiar to you here. If we take as an example the dotenv library and go back to our terminal, we can use the Flow-typed command to search for dotenv and we see shown in the terminal as search results the same results that are seen on the repository web UI.

  35. Fill out Libdef Stub Let's come back to our choose your own adventure project.. When using Flow-typed, sometimes there have not been typings files for those libraries that we're using. In these cases, we have created stubs using the Flow type CLI tool. One such stub is for the lit-html library. For additional type safety here, we would do well to fill out this stub to types that aren't any typed based, but that have some more specifics that will allow us to know if we're actually using these libraries correctly. So let's go here and modify this stub stripping out everything that is not the declaration of the module lit-html. What remains is only the module declaration. Here, note that the module declaration name matches the node module name, lit-html that we have installed. If after saving this libdef, we go back and we run Flow in our project, we see that we have several new errors. Removing the information from our libdef that we did has made Flowtype unsure of how to use lit-html. Taking a look at the first error it says cannot import HTML because there is no HTML export in lit-html. The second error is similar. Cannot import render because there is no render export in lit-html and that is because we have removed the any type based module exports. But now, we can go back into our libdef and specify those functions as lit-html intends. So first, we will create the HTML function by declaring it, then specifying that it's exported as a public API, it's of type function, it's called HTML, and it takes a string array of templates returning a string of HTML. Secondly, let's declare the exported function of render and note the requirement to export here to make physical. Render will take the result of whatever that HTML template is and attach it to a DOM element of document fragment that will act as the container for this application. Note that Element and DocumentFragment are types available as a part of native JavaScript as defined in Flow itself. This is much as capital E Error was defined in earlier modules. And this render function will return undefined here set as void. If we save the file, go back and run Flow, now we have no errors. The HTML and Render functions are used as specified. Now let's go to one of our specs for the index page. Here, we will add the Flow pragma to allow us to type to this test, and now that it's typed, let's run Flow again, and wow. We have a whole bunch of Flowtype errors. Looking at the first, it says cannot call actual.getHTML because property getHTMl is missing in String. We have defined the HTML function in lit-html as returning a string and here that string bound to the variable actual is now being called with a getHTMl function. Obviously, string does not have this built into JavaScript. So is our return type of string as defined actually correct? It turns out it's not. Let's go back to our libdef, and instead of string, let's have the HTML function return a lit-html template result. That template result will then be used in the render function. Now let's declare what is a template result. We will declare it as a class and this class will have the pieces that the test is already exercising. It is using values, which allows us to get the variables inside of the template and it has the getHMTL function that returns the string of the template. Now with our new type in place, running Flow again, we have no errors. And now, we have done much better at specifying exactly how lit-html works.

  36. Specify Return Types Now that we have this type TemplateResult in our libdef, we could specify additional types by coming to the pages index file and adjusting our function types to have this return type of TemplateResult. If we add TemplateResult to the first title function and then run Flow, it seems to have worked. If we add TemplateResult to the next and run Flow again, we have an error and why is this. It says cannot return if props.narrative.image then html, otherwise null, because null is incompatible with TemplateResult and so it is. We have said that a TemplateResult is always returned, but in this case, without a narrative.image, null will be returned, so we simply need to adjust this to a maybe type. And now if we were to use that pattern in the rest of these UI functions, wherever these are potentially nullable, we will make them maybes, otherwise, they will be returned as TemplateResults. GetChoices is an exception because it does not return a template at all, rather it finds narratives, so it will return a narrative array. Now we have typed all of our UI functions. Back to the terminal running Flow. We have no errors and we have additional type safety.

  37. Move Common Types to Libdefs Libdefs are commonly used for third-party code, they can also be used for our own code. Previously in the project, we have created a common types.js file, and within this file, we have types that we are using commonly or throughout the project. Wouldn't it be nice if we could use the store or result types without having to use the import type declarations at the top of each file where they are referenced? With libdefs, we can accomplish just that because by declaring in a libdef file, we are saying that type is available globally. Let's see that in action by moving this common types.js file to a new types directory in the root of our project as the common.js file. Now that this file has been moved, we need to make syntax changes so that this can act as a libdef file. So here instead of exports, at the top level, we will declare the interface and each of these types. We could have put this file in the Flowtype directory, but to demonstrate, we have created a new types directory, but note that we must edit the lib section of the Flow config file in order to pick up these new typings. Running Flow again, let's see what has happened and here we have three errors all the same, cannot resolve module common/types, and this should be the case since we have moved that file into our types directory common.js file. Note that each of these is an import type declaration, which we should not need anymore. So let's go through each of these, data, middleware, and store, and remove those declarations. Saving each file, then running Flow again, we have solved this error and we have the result and store types available globally. After having given libdef some treatment, let's start our app up again and we see that our fun little project is now complete. We've started from scratch showing the use of Flowtype throughout the process of building this project and then have jumped ahead and added Flowtypes to a partially typed project to expose us to what it feels like to add types to a project midstream. With this experience and the fundamental types of Flowtype, we are ready to use this new tool on our own projects. Awesome.

  38. Thank You Thank you for learning with me about the fundamentals of Flowtype. You are now empowered to create types for your own projects and see them mature making your code as solid as it's ever been. Enjoy the new tool and the new sense of confidence in your code that it can help you achieve.