 Okay, I'm getting started. Hi, my name is Wei. I work here at Shovee. I am active on Twitter, on that non-syllable handle on top of the corner. And we have a full talk today. So when I first proposed this topic to be spoken at ReactJS Meetup, I got the feedback that it might not be interesting to the audience. So I was really very worried. I checked the GitHub stars for flow versus typescript. And it's not a very good sign. And furthermore, I am seeing maybe a hundred tweets about typescript until I see one about flow. And it's likely that flow published a new version. So last weekend at JSConf Asia, I put a capitalized flow on my name tag there. And I was asking people around, like, hey, do you still use flow? And from my very incomplete sample of developers I've sold to, people are still secretly using flow. They just don't talk about it. So today, let me be that kid who says, I'm using flow. I've been very confused. And here's how I got less confused. So last year before Christmas, when everything was slow, I was browsing through internal tasks. And here's a new ticket that said upgrade flow to 85. As a background, someone from our team has been keeping our code based up to date with flow. So I never really bothered. And then it was a month later before Chinese New Year this time. And everything was slow again. I was browsing through internal tasks again. And guess what I see? That flow ticket was still there. So now I'm curious. There's a C no evil emoji there. So what's the evil there? So this very innocent, cute little kitty in one fine afternoon punched in the command to upgrade flow. And guess what happened? An undisclosable number of unhappiness bursted out, right? I cannot tell you the exact number. But you can interpret yourself by the number of digits there. So something big happened in 85, right? The flow team published a blog post along the release of 85, explaining the why and how it's behind. When I look back today, the whole article makes sense now. But maybe I was too strange to begin with. So basically, I could not relate to what was actually happening in our code base. The first few times I read this article. So today let me try to begin this talk by trying to relate to you what actually happened with my understanding now. Let's look at a simple function first. Can we see this? All right. We want to look at how a function passes information here. This function takes the string of creature and returns a string that corresponds to the unicorn form of that creature. So cornifying a kitten gives you a kitten corn. A kitten corn is a thing here, okay? It's my companion today. So cornifying a kitten gets you a kitten corn, and cornifying a pony gets you a unicorn. When we export the function down below, Flow will ask us to annotate the creature because exported function parameter is one of the input positions that is required to annotate. We don't have to annotate the return type, but if we do, Flow is able to use that explicit information both inside and outside the function body. So anywhere that value of the function return is used is going to have that type. Now let's migrate the same thinking to higher order components. Cornify now is a higher order component. It takes a creature component and instantiates it with a spiraling horn. But what creature are we cornifying? We don't know yet. They're not as simple as strings. They're react components. So after Flow 89, it provides us with a utile that allows us to annotate abstractly what the component is by supplying just the prop types as well as an instance. But we're not going to worry about instance for today. And now as our higher order component cornify should know the relation between the two components. So cornify should be responsible for deducing and passing the information around. But in order for cornify to know what the input of what creature is, we need to tell cornify what the props are. We can pass that information to cornify by providing a type parameter next to the function definition here. Then cornify is now able to know what the input component is. So it will just put it here. And notice that now we're actually not explicitly annotating the input and the output. At this moment, we're making it implicit for now because we're still waiting for the user to provide an explicit annotation for the props. And notice also how inside cornify, the creature component is actually instantiated here in the return component. Therefore, if at any place we call cornify without providing that props here, at the instantiation step, it is considered an implicit instantiation. Okay, let's move on. And what are the logic behind those props anyway? So here the props on the top, the props should contain the crown prop that the creature should need it. And at the return component, on the other hand, no longer needs crown because cornify is feeding the crown. So we want to take away crown. And we do that by using the div utility, utility provided by flow. And this void appended here is used to go around the potential problem where props may not have crown inside of it to begin with. Okay, now, chances are cornify, the higher-order component lives in its own file. And unicorn, of course, is also going to have its own file. And then the future component that is going to instantiate the unicorn is going to live in yet another file. So the information about the types of those components will come in from a file dependency. Before 85, flow did its type inference walk before merging in the type information from dependency. And therefore it will actually lose those information. And after 85, flow waits until the information from the dependency to be merged in. And then it will do its walk to complete the type inference. That is why flow is now able to catch more errors on those inferred types. But there is a big performance cost to pay if flow walks a long chain of dependencies to learn about props components files away. So it asks that we explicitly annotate within each file's export cycle. So that now it will be safe for flow to assume that here, when it imports the unicorn and the unicorn, both of them are already explicitly annotated. This is also why after 85, flow will ask you to explicitly annotate each module export within one file import-export cycle. Is everybody still with me? We're not at a better part of the story. So we already know that after 85, the code base exploded. And we also know that the higher order components participated in the explosion. So what in our code base are the most commonly used higher order components? Is the ReactRidaz connect? And we're not alone. Apparently, everybody's connected components were exploded. So back then, I had no clue how to fix this. So why don't we just Google it? So I landed on this GitHub issue in Flow's repo. And I'm not sure if I like his words. I see desperate here, painful, complex, and outdated, those kind of things. But at least we're here, right? There's an issue someone already raised in Flow's GitHub repo. So why don't we see what other people say about it? Someone responded to it on the same day, and it goes like that. Have a look at the tests alongside the Flow type library definition. That's the best way to see up-to-day recommend practices. I mean, what do you mean the best way is to look at tests? Are there no docs? Anyway, if we think about it, it's actually not that outrageous, right? Because Flow is not supposed to know about ReactRidaz. And ReactRidaz is not supposed to know about Flow, neither. So what brings them together is this community-maintained Flow type library definition repo for Flow. So after 85, people at Flow type discussed new options for the library definitions and ultimately settled on the new solution for ReactRidaz after V5 because VCA7 are downward compatible. And it's also dependent on Flow version past 89. So here is the signature for Connect. And I'm blinded again. Apparently English is not a good language for abbreviations. And I believe the library author also agrees with that. So they actually provide a dictionary at the top of the file. So now we're able to reassemble what Connect is doing here. There are as many as six type parameters in Connect. But actually we don't need them all because Flow is able to infer some of them. And most of the time, you only need two. And the underscore is a placeholder in Flow that was added in version 84. I have a link to a commit that adds this underscore back on my slide. So if you're interested, you can read that after this. So ReactRidaz recommends three ways for you to connect a component. They are connecting component with MapStateToProps, connecting component with MapDispatchToProps, and connecting the component with both MapStateToProps and MapDispatchToProps. It also recommends that MapDispatchToProps is an object full of action creators. So we'll go with that as well. You might not be able to see this. I'm going to read this for you. So OwnProps has passed through, which is the props that is not concerned with Connect at all. But it's going to pass through your connected component. And then we have four MapStateToProps, which will be the props that Connect consumes, MapStateToProps consumes. And then props will be OwnProps plus the props that MapStateToProps will feed in to your component. So you will be spreading OwnProps here. And take note that when you spread an object with flow, they better be exact. And then, when you annotate this line here, I'm sorry about the size, you connect with the first parameter being the props and then the second parameter, OwnProps. You don't need the remaining four because flow will infer them. And then that's it. And where did I know about this? It's in the test file. Next one, when connecting with just MapDispatchToProps, OwnProps has only passed through because we're not using MapStateToProps here. And then props will have OwnProps plus the props feeding by MapDispatchToProps. And once again, you put it here, props being the first parameter, OwnProps being the second parameter. And where did I know about this? It's in the test. And then finally, connecting with MapDispatchToProps and MapStateToProps. In props, you have OwnProps plus DispatchProps plus StateProps. And then under Connect, you put props on the first place and then OwnProps in the second place. And where did I know about this? It's in the test file. So once we know how to annotate Connect, we feel we're almost there, right? So everybody good with Connect now? Know where to look when you are stuck? All right. Okay, here is the best part. Our team love higher order components. Anybody knows an article called Higher Order Component in Depths on Medium three years ago? That one was on our onboarding guide. So this is our new product detail page. The whole page is wrapped with a higher order component that wraps your content with the header that has the search bar and the footer that will load the footer content based on which page you are in. And then this wrapper actually is wrapped around with another higher order component that takes care of theming so that the page will change theme on the festival season. Every single component here is wrapped around with tracking and error boundary. And then finally, this whole big chunk of recommendation here. Each of those are wrapped around with a higher order component where you can fit in just the item ID and the type of recommendations you need, and it will fit in all the items you need here. We love higher order components so much that we even still compose from Redux and use it on our components. We thought we were pretty smart. Well, wait until we have to annotate this. So let's start. So a component with everything will have all props. We will pass that information to the nearest higher order component which will be with D. So with D receives all props and because with D is responsible for feeding in D, it's going to take away D what it returns. So when we feed into the next higher order component with C, it's not going to have D in it. So we do that by taking the dip of all props with D props. We're just going to assume that with D props is correct. And then the next higher order component that we're going to feed in with the props information list is with B. And once again, we'll take away the with C props and so on and so forth. Chances are we're not done yet because most of the time we'll still have connect. And I'm like, if you were in my situation like three months ago had some hundreds of components like that to fix not to mention that you're not even 100% sure that all of these higher order components are correctly annotated. Will you go ahead and do everything like this? So for the longest time, I was really stuck and not knowing what to do. I did some searches for it, but they were really very echo-less. This time there's not even a GitHub issue. It was just nothing. I checked the test file too. You can check it later. But the complexity doesn't match. And then in this test file, it's not really what they're also annotating the higher order component inside. So that's actually not really what I wanted to do. So we started to question whether higher order component is still a pattern we want or not. And as a matter of fact, that was the time we were urged to consider adapting to hooks so we can peel off higher order component wrapping. And so we actually upgraded our React dependency to 16.8 during that time. But still it is really very unrealistic for us to rewrite all the hooks at once just to make Flow happy. So we're still here. Still stuck. But my philosophy of life is just that if one blocking problem will eventually unblock itself if you stay long enough with it. So one day it all of a sudden occurred to me, like why is it that I was trying to annotate higher order component layer by layer? I think it was probably the ankle brackets because they're so inviting that they kept making me wanting to put stuff in it. So I went back to reading this article, the one Flow published alongside 85. It's a very long article, and when it introduced implicit instantiation errors, it said that you can either annotate with explicit type argument which what we did just a while ago, or you can annotate the return type. Okay, so that's hint number one. And hint number two somewhere else in this article and said that sometimes annotating the return value will be simpler. And that we only have to annotate once within one import-export cycle. So that was like the aha moment. Is everybody ready to see the final puzzle result? So we won't be doing subtraction anymore. We'll be doing addition. We start at own props which contains no injected props. And once again, we prefer to explicitly annotate our props so we merge all our props to be used in the definition of the component. So here we use all props. But when we export, we do not annotate those higher order components layer by layer. Instead, we just say all at once that the return, the exported component is own props. So in this way, we don't have to worry about all the higher order components wrapping at all. And the component is correctly annotated. It has everything. And outside of this component, you only have to worry about own props which is the intended way we want this component to pass the information around. And in fact, you won't lose any coverage by annotating the final export this way because even if you do annotate with explicit type parameters in the middle, each of those is only going to compute what it returns. And we are going to pass that information down to the next layer, which is not really required. That's when we start moving again. It's not amazing. But wait, there's more. I'm sorry to tell you that if I leave you with the impression that everything is about higher order components only, this talk will not be complete. So there are some other issues that we run into. Like from 85, it was the first time we actually put in function type parameters in the beginning. But Pretier refused to pick up that syntax and not recognize them as full type annotations but instead recognize them as expressions. So every time I saved the file, it would change the file to like that. And it would format them wrongly and it would also block our CI. So you spend the next half a day trying to understand completely unrelated issues like what is the difference between Pretier-Yesling, Yesling-Plugging-Pretier, and Yesling-Confect-Pretier. Turn out that our Pretier was more than a major version away. And there was a bug that was fixed in the recent version. So upgrade your Pretier. There's more. Back then, Flow was using a lot of memory and sometimes if I was accidentally running two copies of Flow servers, this 16-gig memory machine will shout at me and be like, hey, your machine is out of memory and started randomly executing applications so it was just super scary. I'm not sure if this will work. There is even more. Sorry, no audio. Sorry, you can enjoy that at home. The final... Okay, why is it playing anyway? I was really very inexperienced to begin with. For example, I did not know what was going on when, say, I fixed one implicit instantiation errors. I would expect a total number of errors to be go down by one, right? Well, sometimes, some other times it will go up by like tens. And I could not see the newly generated errors because my console maxed out at like 4,000 lines of output. So those newly generated errors, I have no idea where to find those. And when the total number of errors bloated up by one digit, I thought I was clearly doing something wrong and I reverted all my previous changes that was just a complete waste of time. It was only after I cleared all the other errors when I finally saw that the new errors where the errors flow was able to catch after I explicitly annotated my components. So here's what I learned. Fix the groups of errors together. Maybe it's time to review some bash commands at this time. So when I fix our second package, and I know what I was doing, what you want to do is you want to fix all the implicit instantiation errors first. Your number of errors will likely go up. Don't worry about that. Fix other errors also in groups. Normally they will have some identifiable phrases so that you can do something similar. And then when you clear everything else, the newly generated errors will come together with very accurate error messages so they will be very quick to fix. So in the second package that I worked on, also some big three digit number of errors, knowing what was going on and knowing what I was doing, it is actually possible to fix them within just half a day with only four commits. So our CI finally passed after three months and it was really very therapeutic and you can do it too. Thank you. So there were a few questions that we asked along the way. Is it worth the effort? In April this year, the Flow Team published an official blog post, an official blog post that aimed at helping people upgrade their flow code base. The secret is when they upgrade, they have an internal tool that will automatically suppress all new errors caused by the upgrade. If I knew about this in January, I would just do that and there would not be this talk. But if Flow fixed me everything, we are actually losing the improved coverage on that version. And in fact the improved type safety is actually a crucial gain. After this upgrade, Flow is able to notice the errors when the props wrapped with higher order components don't match even if you might be crossing file boundaries. And it also provides more accurate error messages that will pinpoint you to which files are reporting the conflicting types. I believe this question has its ground since the Flow Team themselves has also written that recently a bunch of open source projects are migrating to TypeScript. This first sentence in this quote, sorry, I need to, sorry. So this first sentence in this quote is the highlight of that post, like the most popular highlight of that post. But I think we should not take that line out of context. It does sound to me that the people on the other faction are having a better life, maybe with better community support. Although I think a better question to ask is this. Will switching to TypeScript solve our problems? I think keeping our code base up to date with Flow ever since 85. As we speak today we're at 101. Throughout this process, I've encountered problems over and over and I soon realized that my previous understanding about the TypeSystem was really very shallow. So behind all of this, the bigger picture is Flow as well as TypeScript is a TypeSystem or a static type checker on top of a dynamic language. So whenever I encountered a problem, a closest friend to ask was just TypeScript, is this problem specific to Flow or is this specific to our implementation or is it a battle between flexibility and TypeSafety or is it about the dynamic nature of JavaScript? Consider this example. This is a function that takes a number or a string and may return a number or a string. If you try to use that function, plug in the string and expect the TypeSystem to know that fx, when we give it the value to know that it is a string, it won't. We may have multiple function call signatures, but it is not possible to write overloaded function bodies with JavaScript. So the code below will always result in a TypeError. However, it doesn't matter if you are with Flow or TypeScript, you're always going to have this error. Therefore, it is probably not wise to switch faction before understanding what is causing the hair pulling. You might end up putting all the effort to learn the other syntax only to realize that you're facing the same issue. And by the way, this repo here compares the differences between Flow and TypeScript. So if you're really seriously considering switching over, check this out first. And in fact, throughout the past few versions after 85 that have been keeping our code base up to date with, Flow has become better and better at each releases. They've been focusing on performance, reliability, and language tooling. So a selection of such improvements includes making Flow recheck only what needs to be rechecked when a file changes. They built a feature that creates a saved state per commit so that your flow server can start on the previous commit state. They also re-architected major parts of Flow for better responsiveness, for IDE integration or intelligence. It is now using less memory, and there are more and better utility types around React. So instead of thinking of this as an either or, I prefer to think of them in a both mindset. They're both static type checker on top of our dynamic JavaScript. They can both be considered as a compiler. They have some differences, one relying more on type inference like Flow, the other one being more of a bus. I say try them both on different projects and use them as a stepping stone to understanding type systems better. Because it will lead us to think about this next question, if something is very difficult to annotate, does it indicate a bad practice in implementation? And let me throw in just two more final code snippets, I promise, okay? As we discussed earlier, all we discussed earlier about higher order components, they're not hard, they're not difficult. The difficulty was more on the lack of guidance, rather the actual problem is actually not hard. This is hard. Why? This is a function that it may return either a string or a location shape. If you still remember the example two sides ago, it is not possible for you to know from the function call that what's returned is going to absolutely going to become a string. So whatever this type is, it's either a string or a location shape. It's going to be passed into whatever that uses this function value. And there is no way you can refine that on that spot. So you will have to keep writing that if else condition, if you're writing your function, like the very base function this way. So maybe here we'll consider, instead of having a very smart function that returns either or thing, we have two separate functions and we call them at two different conditions that we can control. And this is hard. This is a function that takes a parameter of a type that's either that type or is a generator of that type. And you will expect that type of function, type of unicorn equals to function to lend you the generator branch. It won't because it doesn't know whether this one is a function or not. So this is hard. I would prefer to think of this as a... Well, I don't think the conclusion is whenever the typing is hard, the implementation is bad. No, I'm not saying that. What I'm saying here is when it is really hard for you to type, think about whether we're actually doing a bad implementation or not. So this final question I propose is that we think about this. We initially brought in type system on top of JavaScript to gain type safety, but when our type system is unhappy, we take that as a chance to think whether our implementation is a good practice or not. So this is all I will be sharing as a content today. I have a long list of references, so allow me to navigate you through a little bit. I have many guides. The ones with an avatar are by myself. I have two guides on essentially the same topic, but they were written in different times. And the second one on top, I believe is a better one, but I know the second one is correct. And then I've been keeping our code base up today with flow, so I have nodes on 98, 99, and 100. I'm still working on 101. And then there are more migration guides by other people. I've checked all of those. All of those are saying the right thing. There are a few GitHub issues and commits that are quite interesting for you to read. And then finally, maybe you should re-ask King for required annotations one more time, see if it makes more sense. And then Flow has a resources page that if you're interested in type system, you can take a look at that as well. The URL to this slide, flowbehappy.netlify.com And I want to save time for our next two talks, so there will be no question and answering session, but feel free to reach out to me on Twitter or speak with me offline. Thank you.