Optimize React App Performance
Building dynamic React.js apps that avoid performance pitfalls can be challenging. Shaundai Person will show us how to optimize our components, hooks, and more.
Links & Resources
Click to toggle the visibility of the transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone, and welcome to another episode of Learn With Jason. Today on the show, we're bringing back Shaundai. How you doing?
SHAUNDAI: I'm doing great. Glad to be back, Jason. How are you?
JASON: I'm doing so good. I'm always -- any opportunity to spend a little time hanging out with you is a great day. So I'm looking forward to this. It's going to be a whole lot of fun. So I wanted to just, I guess, let's start for folks who have not heard of you before. And maybe just give a bit of a background on who you are, what you do, all those good things.
SHAUNDAI: Sure. Yeah, my name is Shaundai Person. I say pronounce is like a day of the work. Saturday, Sunday, Shaundai. And you won't forget it. I'm a Senior Software Engineer at Netflix. I work on the frontend. A lot of the stuff I do is in React, TypeScript, also GraphQL, which I didn't know before I started with Netflix. But I've jumped into the deep end, learned a ton. I work on the productivity engineering team, so we make internal tooling for other Netflix engineers. And outside of work, I parent, I do conference talks. I love to speak. And I also am working on a course on TypeScript that --
JASON: Oh, all right.
JASON: I'm just going to drop one of those right into the chat. Here we go. Is this stream going to upload on YouTube? It certainly is. Brandon is in here. A couple first-time chatters. Hello, hello. So okay, you said two things that I think are really interesting. So the first one is you work on you called it internal productivity. Is that what it was?
SHAUNDAI: Yes, yeah.
JASON: So this is a field I find absolutely fascinating because as far as I can tell, it's sort of new. Like not new, new, but last decade or so new, where companies are hiring developers to build their products, but they're also building teams that work on not external products but, like, internal products. So I think a lot of people are maybe hearing about this for the first time because the company has to be a certain size to be able to invest in an internal tooling team. So for folks who are at smaller teams, what does that mean? Like, what are you doing on an internal tooling, internal productivity team?
SHAUNDAI: Yeah, so that's a great question. And it could mean a number of things. At my last company, I did a little bit of that, even though it wasn't a formal productivity engineering team, but the company size was, I think, in total 400 with a very small subset, maybe 50 of those, being engineers. So some of that work we did to serve other engineers was build a component library and make sure it was flexible and extensible enough to get plugged into all the other tools the team had. We had to be cognizant of making sure that it was -- the things that we were building is kind of framework agnostic, language agnostic. We would acquire companies, adopt their repos, their tech stacks, and have to be able to plug in buttons and headers and things like that into the stuff that they already had that was up and running, had users already. So that was a lot of it. What I do at Netflix I find really interesting because I'm feeding my own persona. So one issue -- I'll use the word issue. One challenge when I first started at Netflix -- and is still probably something I struggle with -- is just figuring out what tools do what. At Netflix, we have different teams that will either build a brand-new system that we need, like let's say monitors the health of a repo or alerts you on security updates. So we have all these disparate systems with all of these very cool and clever names, like Volcan and Spinnaker, but it doesn't tell you exactly what that tool does. You're just supposed to figure it out. So one thing is just discoverability. How are engineers, new and senior, how are they discovering what tools they need for their daily work? And how do those tools interact with each other? Then how can we build an experience so that developers -- you know, with your work at Netlify, you're probably very in tune with this. But as a developer, what is your experience like? Like, how easy is it for you to stand up a local environment? How easy is it for you to troubleshoot a weird bug? How often are you notified about different security vulnerabilities? So we have to be in tune with our customers all the time. Then we're finding the problems and solving those problems. I think the fun part is that a lot of it is stuff that isn't done already. We're like building it new or deciding whether there's another company that does it better that we should just buy it.
JASON: Right, right.
SHAUNDAI: There's no precedent for a lot of it.
JASON: So this is -- what I find really fascinating about it is that, you know, each company -- like there's a reason that people are spinning these teams up, and it seems to me that what we're finding is there is the value in something off the shelf. A lot of times you can just reach for, you know, like GraphQL you mentioned is a good tool for bringing together a lot of disparate systems and giving your team a normalized interface. But before GraphQL, what were teams doing? What happens when you have a bespoke internal design system and a bespoke internal reporting system and a bespoke internal thing? You can't use off-the-shelf tooling anymore because it doesn't interface with any of this stuff, right. So you find yourself with these very unique needs, and it costs real money to have a developer who takes a month to spin up because it takes forever to figure out what are the tools, what are the environment variables, how does this thing work, how does that thing work. So I've never worked officially in internal tooling, but I sort of accidently started it at IBM. When I was there, we had this -- we were the frontend team, but the starting process for a day one frontend developer was install engine X, install -- you need to put these firewall rules in place. Here's how you get the reverse proxy running. Here's the docker image you need. I was like, okay, I've been doing this for a long time, and I've never done a lot of the things you're asking people to do, and y'all are running a frontend boot camp where people are coming out after nine months and this is their day one. Like, there's no way this is possible. There's no way this is productive. So I ended up just kind of trying to like, what if we rebuilt this a little bit. I built a little template that you could npm install like IBM boilerplate from our private npm instance. It auto configured all that stuff for you in a way that a developer could just install a new thing, and it was running. And we could do centralized security updates because we had a team, a literal team, whose job was doing a round robin of the 30-plus different teams in IBM cloud and helping them do security migrations for the year. So they just had to go to the team, fix their stuff, go to the next team, fix their stuff. Meanwhile, all the other teams are drifting further and further out of compliance. So it was like this game of whack-a-mole they could never win.
SHAUNDAI: This is my life right now.
JASON: But this is the importance of internal tooling because some things you can't fix. Internal training, you just need to keep doing it. You will always need somebody who goes around and teaching the teams best practices and so on and so forth. But a lot of this stuff, whether or not the software is properly interfaced with, like, the right security rules or the right internal checks or whatever the things are, you can build that in one place and then deliver that to teams as a tool, as opposed to, hey, copy/paste from this repo that did it right, and then everybody has to copy/paste every time it changes. There are these little things you can do, if somebody has time to do it. That's internal tooling.
SHAUNDAI: Exactly. Exactly. Right. And it just enhances from day one. Think of yourself as that brand-new developer who comes in and is like, oh, my god, I just started this new job. Whether it's at Netflix, whether it's at a smaller company, you're feeling a little bit imposter syndrome or overwhelmed with all the stuff there is to know. Just imagine that developer comes in on day one and is able to push up a PR or write some code that goes into production eventually. Maybe not on day one, but it just does so much to enhance that person's experience at the company from day one. Then things go on from there. I think it just makes your company more of a -- your engineering team more of a well-oiled machine overall. So it's important.
JASON: Absolutely. Yeah, I think it's -- so anyway, that's not what we're talking about. Well, we're kind of talking about that today. But one of the things when we talked last time, one of the things that I enjoyed the most is how good you are at analogies. I remember we were talking about React and React children and all these things. So that's part of what I'm really excited about today. We're going to get into something that's very heady, which is React performance. I think we're going to need you today because this is stuff that is confusing, no matter how long you've been in the game. So let's talk a little bit about this. Where do we even start? Where's the right place to start with react performance?
SHAUNDAI: So the first thing I want to say is that the things I talk about, like a lot of people will ask, how do I? How do I optimize a React app? Obviously -- maybe not obviously --
JASON: (Laughter) obviously.
JASON: Yeah, and this is one of those things that, as you said, is so context dependent. Because I think one of the biggest pitfalls that almost every engineer falls into when they first start doing things with performance is they start to think about performance at the expense of everything else. And you'll see people write really convoluted code or walk away from a known solution or whatever it is because they'll say, well, I heard that's not performant. It's like, my dude, you're putting five components on a page. You're not at a stage where you have to worry about micro-optimization. Like, you can worry about these things when you get to the point that performance is a bottleneck. But in the meantime, just, you know, make the thing. It's okay. You can just build it and not worry about whether or not it's performance tuned. (Laughter)
SHAUNDAI: Right, exactly. Yes, and your overall goal, in my opinion -- and I always feel like I have to caveat because there's always someone that's like, no, in my case it's different. In general to me, the most important person or the most important persona you should be thinking about in your code is your user. Strong second is the developer, especially if you're working on a team. You want to enhance that developer experience. But think about what your user is going to need, what features your app is going to need to serve your user in the best way. Then work backwards from there and say, okay, well, if I am going to be fetching huge amounts of data or if I need to display huge lists of information, think about your users who have slow internet connections or take into consideration the fact that when you're server rendering, you have all this data all the way on the server, and it's going to have to travel over the network to the client. How can I minimize this as much as possible? How can I minimize the load in case my computer has slow internet or working from a phone with a really bad connection or something like that.
JASON: And you mentioned the different phases too. There's the initial load of the HTML. And when you do hydration, if you've got a big list, you're not only putting that list into the HTML, you're also putting all of the data for that list into a big JSON block at the bottom of the page that then has to be read into memory and parsed and all the those things. So when you get into huge lists, you're shipping it twice, and you're processing it at least twice. And that's when you have to start worrying, when you've got thousands or tens of thousands of items. You probably don't want to server render that whole thing. That's going to take forever to load. It's going to be laggy as hell, and if anybody has ever tried to render 10,000 items into a list, you probably saw React not do well with that. (Laughter)
SHAUNDAI: Yeah, (laughter). And it's funny you mention that. As I was preparing for this, I tweeted at you yesterday about how I crashed two times. I was playing with a little demo I was working on, and I was like, oh, this is simple because it was just a list of things. Literally it was like a div and two words. So one word was movie. The other word was like the index. So one, two, three, four, five. So I was like, okay, if I render a list of a thousand, cool. That works well. What if I did a million?
JASON: Oh, my god. (Laughter)
SHAUNDAI: And it didn't work. I was like, okay, not going to do that again. But I know my limitations. But that's only like mapping over information of not fetching data. I'm not rendering images or showing videos or streaming. So at a company like Netflix, for example, Facebook or Google where you have huge amounts of information coming in or let's just stick with Netflix. If you're scrolling on the Netflix app on your phone, there's a little preview of a movie. So you have all of this data from the movie that's streaming, the couple seconds of video, the images, and then all the metadata that goes along with it. Like how is this relevant to the user? Who's the producer? Producer? What's that called? Oh, director. Director of the movie. Like who is the director, the actors, things like that. So all that information needs to go in. That's very computationally extensive for your browser, whatever device that you're using. So, yeah.
JASON: Well, especially with something like Netflix. It's not like we can just hit an API and load all movies and have it ready when you need it. It's thousands and thousands of movies and so many data points for each movie. You know, chapter markers and all of the metadata. All that stuff and a bunch of personalization too because it's got whether or not it's recommended for you. It's got ratings. It's got similarity to things you've already watched. Like, so much information is being sent in a Netflix UI. You can't just download it. So you got to be smart about when that data comes in, otherwise you're wasting people's data. You're going to slow down their computer. It's no joke.
SHAUNDAI: Exactly. Yeah, and we pride ourselves -- even before I came here, I was like Netflix is just so easy to use. But we really take pride in the users' experience, more than anything I would say is just how seamless is it for a user to get onboarded and how often are we having down time, which hopefully is never. But it's not a perfect world. Yeah, we put a lot of effort into performance. So that's a big concern. If anybody is looking, just know that.
JASON: Okay. So let's talk a little bit about what -- you said virtualization is what we want to focus on today. So let's talk about what that means. So this is a concept that I hear people say, but I don't know if I've ever actually heard it explained in a way that has really made it make sense. So what I think in my head is I have 10,000 items that I need to put into a table. I query the database. I have to put those things in a table. That makes the page slow. Then somebody says, ah, virtualization will solve that. I go, that sounds like magic. Then I stop. My brain stops accepting information.
SHAUNDAI: That's pretty good. You're like most of the way there.
SHAUNDAI: What more could I ask, for real? So yeah, virtualization, that was actually a really good segue because of all the stuff that we were talking about. So think about -- like let's use movies as an example. If you are trying to render a list of movies in an app, let's call it Betflix, right. So creative.
JASON: Totally made-up app. (Laughter) It is not similar to anything else out there.
JASON: We're Vogueing. (Laughter)
SHAUNDAI: Yeah, just imagine that. And you can think about this if you think about Netflix, Facebook, Pinterest. As you scroll and things are new to the dom, those items below are going to get painted to the dom. As the items get out of your viewable window, we're removing them from the dom. So the browser doesn't have to keep all of that stuff in memory, and we don't have to load all of that information. So it's called virtualization or windowing.
JASON: Thinking about this from the standpoint of, okay, we've got our window here, right, then if I'm looking at this box here and we have lists that are coming in, if I can fit ten things on my screen, then I'm probably only ever rendering, say, 12 to 14 of these items because as they come out of view, they're going to get removed. Then a new one gets dropped at the bottom so we can scroll up and down, and it looks like the whole list is there, but as far as the computer is concerned, it's only ever handling like a dozen or so of these components, which computers, in the dozens, they got it. It's under control.
SHAUNDAI: Exactly. Right.
JASON: So the idea then is you are using the magic of the view port as kind of an illusion. So you switch from being somebody who is strictly building a full list, where it's kind of that joke about you see somebody in an old timey movie and it's a list of commands in a scroll and it unfurling out the door and all that. That's the original way. And this is moving more toward, you know, like a flip board. You're only showing what needs to be shown, and then you get rid of it and look at the next one. I don't know where that came from, but you're a good influence.
SHAUNDAI: Yay, (laughter). That was great. That was really good. I'm going to use that. That's awesome. But yes, yes, exactly like that. Just like using a flip board. So React is just like short-term memory, forget it, after it scrolls out of the viewable window. So it saves your browser from having to hold on to all of these calculations to keep stuff that's rendering that nobody is even looking at.
JASON: Okay. All right. So at that, we're about 30 minutes in now. So I really want to start putting some code together here and actually show how this works. I'm going to take this opportunity to flip us over into the pair programming mode, once I get this window out of the way. And now here we go. Camera -- oh, no. What happened? Where am I? Okay. Everybody hold up. I'm going to fix my goofy screen here that I just broke. Why did you -- what? Okay, hold on. It says I'm here, but I'm clearly not here. So we're going to do one of these. Then we're going to do one of these. What's happening? Why are you like this? (Laughter) Maybe one of these. Nope, not like that. Going to do -- oh, okay. Here we go. We're almost in business. What are you doing?
SHAUNDAI: I'm managing like you're opening a curtain or something.
JASON: I don't know. I'm just going to reset this transform entirely. Now I'm way too big, but that's okay because we know how to handle that problem. And we're going to move over here. And that's about the right size. And we're going to call that close enough. Okay. (Laughter) I don't know what that was. Okay. So with that, let's get down to business here, everybody. No fun allowed on Learn With Jason. We're going to talk about -- we have Shaundai on the show, and with Shaundai, we also have live captioning, which is being provided by White Coat Captioning. Today on the show we have -- why can't I get this tab? Just, you know, I'm getting -- everything is making me nervous. We have Rachel with us today from White Coat Captioning taking down all these words. Thank you very much, Rachel, for being here. And that is made possible through the support of our sponsors, Netlify, Nx, and New Relic, whose SVG I've broken. I think it has something to do with the browser blocker tool that I can't debug. Because I keep forgetting to do it. So New Relic is in there. It'll probably show up on your browser. I'm in Arc and it's helping. "Helping." We're talking to Shaundai today, who you can find. Is Twitter still the right place to send people? I'm not sure. But I'm going to do it for now. So we're going to drop your link. You hanging out on Twitter these days, or are you on Mastodon?
SHAUNDAI: I'm on both, but I'm actively using Twitter. I'm not sure what the etiquette is. Do I post to Twitter and also post the same thing to Mastodon? I felt like I was copyright stealing from myself.
JASON: I feel weird about the whole thing. I don't know. I like Mastodon. It's also very hard to use. Aisle confused by the whole thing. And here we are. I just really -- maybe we get Twitter back. Like, just give it back. Give it back. I want it back.
SHAUNDAI: Yeah, I'm thinking about starting a GoFundMe so we can all buy it back.
JASON: All of $44 billion. (Laughter) If everybody kicks in, what is that, $8, $9. We can do it.
SHAUNDAI: The babies too.
JASON: That's right. Parents, chip in for your kids. Yeah. All right. Should we talk about code? (Laughter)
SHAUNDAI: I could talk to you all day about other stuff. Yeah, we can talk about code.
JASON: All right. What should I do first if we're going to do some React?
SHAUNDAI: Do you use VSCode? I think you do.
JASON: I do.
SHAUNDAI: So let's just do a new create React app.
JASON: And so if I -- what is it, like, npm create --
SHAUNDAI: Oh, yeah, what is it?
JASON: Oh, we're doing legit create React app.
SHAUNDAI: That's how I always start it. That's one of those things that, you know, I just do it.
JASON: Oh, you have to give it a name. We're going to create React app, and we'll call this React perf optimization. Or what did I call the episode? Optimize React app performance. That way I can find things later. And we're installing the things that we need so that we can get this thing up and running.
SHAUNDAI: Okay. Cool.
JASON: I've like given in to the Vite hype. I'm always using Vite instead of create React app these days.
SHAUNDAI: I've never used Vite. I'm guessing you like it.
JASON: It's great. It has all the things that I like about create React app, and it's also using like a faster tool chain. So no more Babel webpack. It's fast and snappy. For my apps at least, it's been exactly what I need.
SHAUNDAI: I'll try it.
JASON: So we're looking at a basic site. I'm going to pop this open in code. And make it a little bit shorter so we can actually see everything. All right. Oh, wait. I got to git initialize this or it's going to be all dim. So let's get init. Then we'll open it up again. And now we got -- now we can see the code. All right.
SHAUNDAI: Perfect. Okay. So we're going to -- I think in the last one, I went through create React app and the parts of it. I think we can skip over that for this time. But for those who -- if anybody isn't familiar with create React app, it is a scaffolding tool that helps you to create a React app. Couldn't have guessed that.
JASON: Good naming.
SHAUNDAI: Right. It's very clever. The upper most parent is called index.js. That's where our app is being exported from. Then app.js is typically the oldest child, the first child underneath it. So we'll work with that. What we'll do is we'll start out with an app that's not virtualized, which should be a quick list of movies. Then we will add some virtualization. So there are tools that you can use to help with virtualization, like React Virtualize I think somebody mentioned in the comments. That's one of the more popular ones. That was created by the amazing Brian Bond. This is -- you can use that. I'm not saying not to use it. This is just kind of giving you a peek under the hood so you can see how virtualization works. Yeah, so Jason, why don't we start with -- let me make your screen bigger so I can see. Why don't we just get rid of pretty much everything that's being returned in this app. Yeah, and then we don't even need that class name either. Or we can keep it. Yeah, perfect. Let's make a to-do list. So the first thing that we want to do is make an array of things we're going to work with. An easy way to do that is to use array.from. So we can create a new variable. Yeah, items. Then inside that -- so array.from is going to take, in this case, two params. The first, let's call it an object length is the key. So just put an object inside of that, like the curly brackets.
SHAUNDAI: Yep, and length.
JASON: Like how many items?
SHAUNDAI: Oh, just the word length. We're going to -- oh, just write the word length.
JASON: Like this?
SHAUNDAI: Yep. Then colon. And count. Because this is going to be a piece of state that we're going to set.
JASON: Oh, I need to put this inside of my component then, huh.
SHAUNDAI: Yeah. Oh, yeah. We should import state too. But we can get to that in a sec.
JASON: So we're going to do like a use state.
SHAUNDAI: Yeah, for count and set count. Perfect. And let's start with a thousand. A million is going to break it.
JASON: Oh, you want to see a trick I learned that makes me really happy? You can put an underscore in numbers to separate them so you can see, and it doesn't break the number. This changed my life.
SHAUNDAI: Wow. I'm learning. That's amazing. I'm going to use that. I love it. Okay. Perfect. So we have our count. And so what this items array is going to do is spit out a length -- sorry, it's going to spit out an array of objects. Let me explain this better. So what we will do is -- actually, let's add this function first. So the next parameter is going to be the function. Then what it will return is an object with index. Yeah, index is one. Then make that i plus one. The first is going to be an underscore.
JASON: Then the next one is index?
SHAUNDAI: Yeah. So this is one trick that I recently learned. You know how parameters, you have to make sure that they're in the right spot for React to recognize it in the return or whatever? If you have a param you're not going to be using but you need the second param, like we need the "i" in this case, you can use that underscore and it's going to ignore that param.
JASON: That's really nice. See this one up here is yelling at me because I'm not using it. But we can do one of those, and then it'll leave us -- how dare you. It usually works. Here, if we were to do value, then it's going to yell because it's not used. But then you just underscore that shit.
SHAUNDAI: Exactly. (Laughter) I love it. So we'll return the index plus one. The reason we're doing plus one is because the index we start at zero, and this is just going to be what the movie name is. Then the next one will be name as the next key. And let's call it movie and then i plus one. Yeah, perfect. Okay, cool. So we have our list of items. So to explain array.from, what we're doing is we have a plus, and this is going to create a list based on the count that we've set, which is a thousand. It's going to create a list of a thousand things, a thousand objects that have the index in the name, and it's going to increment them by one at each step. So the next thing that we'll have to do is we'll have to map over it. So maybe let's create a function called display movie items. Or something. So do that above -- or under items.
SHAUNDAI: Yep. And then we're going to map over this list of items.
JASON: Should I pass it in like we want to send the items in?
SHAUNDAI: We don't have to because we declare items --
JASON: I guess it's all in there.
SHAUNDAI: Yeah, so the way that I would do it is I'd create a new variable inside of that function. This is all a matter of personal preference. But you could do it exactly that way, Jason. Let's just say movie list.
JASON: And make that the map?
SHAUNDAI: Yeah. Then we'll map over that. And then we'll display for each of those -- let's put a div. Because divs are fun.
JASON: We need like a key on these, right? So it won't yell at us. So item.index.
SHAUNDAI: Yeah, and then inside of that div, just put curly brackets with the name. The item.name. Cool. We're going to add some styling but pretty basic styling. We can do that in a sec.
JASON: So down here I want to call this, display movie items?
JASON: Okay. So saving this. We get our movies. Here's the giant list of movies. And every single one of these is in the dom.
SHAUNDAI: Exactly. So what I want you to do, if you can, can you open the console to see how all of the divs are showing up in the dom? Yeah, we got to go in, in, in.
JASON: So many.
SHAUNDAI: Now imagine if each one of those had its own data request or images or videos, how extensive that would be. Like I said, I crashed my computer twice because I made this list a million instead of a thousand, being silly. All it is, is just words. So that's why we want to virtualize it. So if we're looking at Jason's screen right now, we can only see like 40 of these things. So the technique of virtualization is going to make it so that only the items inside of the user's viewable window are going to be painted to the dom. As we scroll up, those items will get removed. So the trick, when it comes to virtualization, is we're going to have two boxes. One outer box is going to be responsible just for the scroll. Then the inner box is going to be responsible for placing -- it's going to be responsible for kind of containing the window. How we have done things with our non-virtualized list is we just stack things up as we're mapping over them, and they just fall where they fall. The trick is that for this inner window, we're going to have this as position relative. And then all of the child elements are going to be absolute. We're only focused on what the top is for this window and the bottom is for this window. Anything as soon as it falls outside of that is just going to be not regarded by the dom right now.
JASON: I got you, okay.
SHAUNDAI: So I see you put the inner box and utter box. And you gave them class names so we can give them stylings. Perfect. Cool. So now that we virtualized this, this is actually a trick that I found from Stack Overflow, which has been very helpful for me. We'll talk about how to use this tool called -- or this prop called scroll top. So create a new piece of state underneath our count called scroll top and set scroll top. So we'll have this as a piece of state called scroll top, but there's actually an attribute on current target, when we have an event. So we're going to have -- based on the scroll event, we're going to calculate what the scroll top, the index of the top element is and the index of the bottom element. We'll use that to be able to determine whether or not movies are within our viewable window. So this one is a piece of state. So let's set an item -- let's set a height for each of these items first. So let's do create a new variable right underneath where you put the state. Then just call it item height. Let's just say 30.
JASON: Then we want to style it here?
SHAUNDAI: Yeah. Does that work with just a plain value? Yeah, there we go. Okay, great.
SHAUNDAI: That should work. Yeah. That'll work. Then we want to do a window height so we'll use it to calculate it. So declare another variable called window height. And we'll just do like 500.
JASON: Okay. Then that's going to be in our outer or inner?
SHAUNDAI: That is in our inner. Our outer one we're going to have to add a style. I don't know if you already did this, but we have to add in the overflow Y will be scroll.
JASON: Okay. Overflow Y scroll. Okay. And now we can see that it's doing the thing. So now we're scrolling this box, and we can prove this too. So let's go with a border of one pixel, solid red, so we can see what's going on here. Now we can see here's our actual box.
SHAUNDAI: Yes. Perfect. Okay. Awesome. And we added a scroll to it. Do we have a height for the outer -- no, we don't need a height for the outer box. Okay. Cool. So we're good. Then the height for this one should be -- oh, one important thing is we need to add position relative to inner box.
JASON: Position relative to inner box.
SHAUNDAI: Correct, yep.
SHAUNDAI: Then I think we should be good. All right. So let's add a scroll event to -- so let's go up. We can create a new function called on scroll. So this function is where we're going to set the scroll position. Sorry, we're going to set what the scroll top is. So this function is going to take one parameter, which is "e," like event. Or you can call it event.
JASON: I am going to call it event because I always forget.
SHAUNDAI: You do? Okay. Let's do that. Then the only thing that it's going to do is set the scroll top. So the function we declared above, set scroll top to event.currenttarget.scrolltop.
JASON: Okay. So this, whenever this gets called, is going to update. Ideally, we're scrolling here. So we're going to change the value of the scroll top, and we don't need to worry about -- like this is where somebody would start panicking about performance. It just happened in my brain. I was like, oh, but every time you scroll, isn't that going to be resetting a lot? Yes, it doesn't matter. Computers are fast. (Laughter)
SHAUNDAI: Right. Again, there's trade-offs. So yes, that is a trade-off, but what you're trading off is not rendering thousands of movie titles and all the data for all of those titles.
JASON: Right, right.
SHAUNDAI: Yeah, in our case it's going to be beneficial. So let's add on scroll to our outer div. That one is the one that's responsible for the scroll.
JASON: So nothing is happening -- we won't see anything change right now. I guess I could console log it. And we could see the same value here. Let's just grab that.
SHAUNDAI: Yeah, you can do that.
JASON: We'll throw one of these, do a little save. Boom. There we go. All right. Now I'm going to turn that off because the console logs get a little messy.
SHAUNDAI: It's looking good. Awesome.
JASON: So we're setting the scroll top now.
SHAUNDAI: Okay. Perfect. So then the next thing that we have to do, or the next piece of the virtualization is that each one of the items that we have needs to have its own height, width, and needs to be positioned as absolute.
SHAUNDAI: So we're going to need to add styling to each of these items. So in our div, let's add some in line styles. Make sure the position is absolute. Width, say 100%. And I don't know if we set a width to the inner or outer divs.
JASON: We didn't yet.
SHAUNDAI: Okay. We'll go back and do that. We also have to set a top position. So what this is going to be doing, let's take a step back. Now we know what the top of our viewable window will be. We know what the bottom of our viewable window will be. So rather than just stacking divs up where they go like you would do as a return from a map function, what it's doing is saying this is where the top of this div is right now. So absolutely positioned. This is where it should be right now. So we're individually kind of placing each item where it needs to go based on this top prop that we're going to add in right now.
JASON: Oh, I gotcha. Okay, okay.
SHAUNDAI: So we have it positioned absolute. So what the top is going to be is a template literal. Then inside of that, we're going to do the items index. So item.index and do that multiplied by item height. Then pixels will go outside those curly brackets.
JASON: So that starts us. We're all where it should be.
SHAUNDAI: Yep. Okay. Perfect. Then another thing that we'll have to do is set a start index and an end index. I hope I'm not getting too confusing, but what this is going to be is based on what we know about the top position and the bottom position, we're going to calculate which -- what the index of the item that's at the top is and what the index of the the one that's at the bottom one is. Then that's how we know what's in our viewable window. Up at the top, we could do this probably underneath window height. We'll declare two variables. The first one will be the start index. This one, the math works. Just don't ask me how it works. We're going to do math.floor. Then we'll put in scroll top, and that's going to be divided by the item height. Perfect. Then let's go another one called end index, another variable. That's going to be the same thing, except that instead of scroll top, it's going to be scroll top plus the window height. So that's telling us the index of the lower -- so we could leave it like this, but we also don't want to go past the last item in the list. So we're going to wrap this entire math floor into a math min.
SHAUNDAI: Then what math min will do is it takes parameters and give you the minimum of whatever is in there. So we'll do count as one param for this. So math min, yeah, and that's it. So now we won't go past the last item in the list.
JASON: Got it, okay. So what we have done here, to break this down, is we're taking the scroll top, which is where we are in the window. So right now it's zero. Then we're dividing it by the item height of 30. So zero divided by 30 is zero. So we keep it all the way up. We'll get the first index of zero. If we have a scroll top of 300, then 300 divided by 30 is 10. So we would have a starting index of movie ten, right? Then here we're just adding the window height of 500 so that's giving us our -- how many items would fit. So when we were talking earlier about on an average screen, right now we can fit 15. With 500 pixels, we'll probably get, let's see, 500 divided by 3, we're going to get like -- I can't do math. 18? That's not right. My math is terrible.
SHAUNDAI: 500 divided by 3. 66, so you were close. You said 500 divided by 3 or 30?
SHAUNDAI: Oh, 16. You were close.
JASON: And I am zoomed in. This makes more sense. I am going to zoom back in a little bit.
SHAUNDAI: I'm glad we got to see that. You got to do the math first. Yes, that's the index for the item that would fit at the top. Then the item that will fit at the bottom. So React is like between those, those are the only ones I'm going to paint. So now instead of using just our array that we created with the full-blown list of 100 items, what we can do inside of our display items function is we can slice and say out of this array, I only want items between this number and this number. Map over those, and those are the ones I'm going to be showing. So, boom, you got it. You got it.
JASON: So slice is dope because it just says give me the items from this one to this one. And then we will map over this instead. And -- I broke it.
SHAUNDAI: You know what, I remember. We forgot to add height to the outer div. And let's make sure it's right. The funny thing about CSS is that it doesn't always -- it's not always intuitive. So you do have to set a height, width for the outer one. Then we could do -- yeah. Let's do height is -- what did we say? Let's do width is like -- that's perfect.
JASON: That's too many. Then we can do one of these margin like zero auto so it's centered. Ta-da.
JASON: But I've broken something else.
SHAUNDAI: Let's see. Position relative.
JASON: Does the inner box need a width?
SHAUNDAI: It shouldn't, but change that window height -- do we have an inner height? I think that's why. Sorry, we should have a variable called inner height, but I don't think we did that yet, did we?
JASON: We did not.
SHAUNDAI: Okay. So yes, all right. The outer box is just responsible for the scroll. Let's make that -- oh, sorry. That should be count times the item height.
JASON: Inner height. I'm breaking something else. I have clearly typoed something. So let's poke around in here and see what we're seeing. It's not rendering any items, which means that something I'm doing is not working, which means we are going to play the console logging game.
SHAUNDAI: I love this game.
JASON: So we'll get displayed items, start index, and end index, just to see what's going on in our code. Displayed items is zero. Oh, not a number, not a number. We made a mistake. What mistake did we make?
SHAUNDAI: Nice. I love this. I really do.
JASON: Oh, scroll top has got to be initialized at zero is why.
SHAUNDAI: Oh. Should have caught that.
JASON: When you divide undefined by anything, it's not a number, which is -- you know, that makes sense. That's how math works.
SHAUNDAI: Should have caught that.
JASON: Oh, look what's happening. See it popping out? Bow, bow, bow.
SHAUNDAI: Boom-bam. Look at that. Now, console log that. Remember how we had a thousand movies on the console? Sorry, in the elements tab. Now let's look at it.
JASON: As I open up all my divs. One, two, three, four, five. There's 17.
SHAUNDAI: Now if you scroll, look at that. Then you go up and see it's gone.
JASON: They're dropping off.
SHAUNDAI: Number one is gone. Number two is gone.
JASON: And this is -- where this is going to matter is not in this list. I mean, a thousand is not nothing. But -- should I do it?
SHAUNDAI: You're going to do it? Bye, everybody. Let's do it.
JASON: All right. We're going to try it. Here we go. I have just rendered one million items.
SHAUNDAI: But -- okay. So this is good because it's virtualized.
JASON: It's virtualized. It's struggling a little bit, but that's because we're slicing a million-item array.
SHAUNDAI: But if this is not virtualized, I guarantee it would break. I guarantee. Because I've done it twice. I did it pretty quickly. Also, if you want to mimic the appearance of a user that doesn't have a fast internet connection, you can disable -- what is that called? So if you're using Chrome, you can --
JASON: Are you talking about throttling?
SHAUNDAI: Throttling, yeah. You can enable that, if you want to.
JASON: Here's my throttling.
SHAUNDAI: Yep. Excellent.
JASON: The absolutely brutal -- I mean, we're also doing a development thing here. But look at this go.
SHAUNDAI: That went so fast. I'm telling you guys, break your machine so you can see how much of a change that it makes to virtualize your lists. This went really -- this looks really good.
JASON: And this is going great. So we'll go back to no throttling. But this is great. And we can outrun it a little bit. If I'm really racing, I can get a little ahead of my virtualizer. So you could, theoretically, say if it's zero, you could go like three items out of frame in either direction. That's also a way to make sure that stuff works.
SHAUNDAI: Exactly. Yep. So you could add -- how would you do that? Like add to the start index. You could add -- sorry, the end index. Add plus three or something so you could fetch the data for the next, you know, three items.
JASON: So plus three. Then we could do the same thing over here with the math.min of zero. Or item minus three. So now what we'll get is broken code. What I did miss?
SHAUNDAI: I wonder, is it that minus?
JASON: Might be. Oh, math max is why.
SHAUNDAI: There you go.
JASON: We don't want it to go below zero. So now it's letting these movies be up here, but we can see in the div itself, if I shrink this down, we're looking at movie five, but we've got movie two in the dom. Then it'll kind of drop out. Now we shouldn't be able to outrun this. No matter what, we should always be looking at a complete list.
SHAUNDAI: This looks great.
JASON: So that's sort of the magic here of this sort of virtualization. And you mentioned before that there are libraries that kind of do this for you so you don't have to roll your own every time. Probably a lot of edge cases we're not considering here. But pretty dang cool that we were able to put all this together this quickly.
JASON: Right. And so for example, on the Netflix app, you are loading, say, the preview video, which those preview videos are as optimized as they can be, but they're still videos. So they're big. It's a lot of data. To try to load, you know -- you were talking about GraphQL earlier. One of the reasons I think people look at GraphQL is because when you start talking about Rest, typically Rest APIs are built on normalized databases. So you would get -- your first query would be give me all the movies. It would give you back like IDs of the movies. Then for each movie, you got to take that ID, so give me the movie with ID X. That's give you the title and stuff. But when you want your ratings, it's going to say here's an array of ratings. Then you have to look up those ratings and aggregate them. So you're suddenly looking at -- like for one movie, you might have to make five or six or seven or more network requests to load that data. That's before you have to load the assets, like the trailer and the poster and any icons or whatever that have to come in. So you might be looking at over a dozen requests for a single movie. If you try to do that for a thousand movies on the page, you will crash the browser. Period.
SHAUNDAI: Oh, absolutely. Absolutely. Yeah, and think about all the different filtering and sorting that you have to do. So if you're going to build Netflix, say you might have the movie "Frozen" appear in recently watched, watch again, but also in Christmas movies and also in children's movies. So you have to have different ways of filtering over that information. I'm really glad you brought up GraphQL. Yes, it does that. When you think of -- like a list like this is very simple. We're just getting the names of stuff. But when we think about the ways that we want to make data requests in a way that keeps our app performant, we only want to grab the data for the information that's relevant to the user at the time. If we don't have to get it, we don't get it. So for a list like this where we're just displaying movie titles, we would want to make a call that only gets the names of those movies. Then we would want to avoid -- GraphQL is great at this because it lets you establish connections between normal -- like if you were using Rest, you might have to make two completely separate API calls. With GraphQL, it makes connections between different pieces of data that might be linked to different edges and nodes inside of your data org structure. But anyway, as you scroll through these movies, you don't want to have to fetch all of that data for movie 19 if that's not in the user's viewable window. Or you don't want to have to fetch the information for movie 20 if the user hasn't clicked it yet. If that information isn't going to be displayed, don't worry about it. So infinite scrolling, virtualization are all tools you can keep to help you not have to do -- or help your browser to not have to do as much work when it's not necessary for the user.
JASON: And it's like one tool in your utility belt, as a React developer or as any developer really. So in addition, you mentioned a couple other things that would require some additional techniques. If we didn't want to pre-fetch things, like let's say each of these is -- actually, let's just look at Netflix real quick. Because there's a lot going on here. Oh, no. I'm not signed in. Dang it. Okay. Hold on a second.
SHAUNDAI: You don't watch Netflix all day?
JASON: I have it on my TV.
SHAUNDAI: Oh, oh. That makes sense.
JASON: Okay. So I'm getting logged in here. Okay. So here we go. Here is Netflix. On here, we've got all of this information. Then if I hover over this, it's going to pop up, and there's this information. Then if I click on it, there's even more information. Oh, I don't want to see it though.
SHAUNDAI: It starts to play.
JASON: I thought there was like another, like, look at this kind of thing. Oh, it's here. The more info. So this more info, then there's even more. There's cast, genre, et cetera, et cetera. Then it starts doing recommendations. This is a ton of information. Like, look at everything that's being loaded in here. And each of these is a trailer. It's got all this extra stuff. I can do interactions and things like that. So each of these -- like two big images to load in. You've got the trailer. If I hadn't turned off auto play, it would be pulling in a video. There's so much going on here. And let's see, one, two, three, four, five, six, seven on screen per row. That's 21 movies right there. 21 movies worth of queries is a ton of data, and the chances that I'm going to look at all 21 of these is super low.
SHAUNDAI: Exactly. Exactly. So yeah, for example, you're hovering over "Wednesday," then we might pre-fetching the data when you hover over it. Then we actually fetch the data or actually bring back the data so it's there in time for you to see the little modal that pops up. We might make an additional request once you click into the actual movie. Or click into the information with all the details about it. So yeah, like you said. And if you think of all the different titles Netflix has and all the data that has to go into it, it's a lot. The connections between different movies, like this movie is similar to that movie. So that's how -- and Netflix, the TV app, is run on React, by the way. That's how you keep it performant.
JASON: Right. So we're looking at virtualization for the list itself. You know, we've got all these movies in here. We don't need to load them all. Then we've also got, like, pre-fetching, selective fetching, something like if I hover, making queries then so that when I click this, it's probably already there. Yep, there it is. And so you end up with all of this, you know -- it's layers of performance. And those choices have to be made for real reasons. If I was making a new service with just movies I've made, for example, I have maybe two, right. The videos that I've made or something like that. Or if I'm doing Learn With Jason, I care more because there's over 300 now. If I try to load them all and get all the data, it's too much data. I actually ran into this problem where with transcripts, it's over 10 megabytes of data if I download all the episodes. Now I have to make choices about which thing I download, at what time I download it, or else I'm bogging down the computer and bringing in tons of information for no reason. The homepage, or the episodes page on Learn With Jason is becoming a problem because I don't virtualize. It is just every single episode, all the way down. Like, this is terrible. I need to rebuild this so badly. (Laughter)
SHAUNDAI: Yeah, we should do one where we rebuild your site. Just add in virtualization. Let's go for it.
JASON: (Laughter) Well, I mean, with what we've done today, I feel like I can go in and make a pretty solid dent in this right out of the gate. Where did my -- wait, what am I doing? Oh, I wanted to resize the window. Instead, I was creating a split view. Let me get over here. I'm still kind of figuring out how Arc works. I really like it, but it's got things. It's got things going on I'm only sort of familiar with.
SHAUNDAI: It's got things.
JASON: So looking at our code here, when do you reach for this? I guess maybe the better question is when is reaching for virtualization not the right idea? When are you distracting yourself instead of getting work done?
SHAUNDAI: Yeah. So I would typically think of virtualization as a technique when I have a large amount of data that's going to be -- a similar thing. So like a list, a table, a chart of information. So anything when you're mapping over something, that's when I would use virtualization.
JASON: Over a certain size, right?
SHAUNDAI: Yeah, exactly. So if it's like a list of things that might go -- even if our movie list was 19 things, it just goes off the page a little bit, it might be overkill to do it. If we're using virtualization in a way like we're using it here, where we're writing all the code and not importing a whole other library, you know, you might want to go for it. To me, it's kind of excessive. But if you have lists of, you know, hundreds of things where users are going to have to be scrolling through things, that's definitely when I would reach for something like that. If you are going to be fetching data from that list, I would say infinite scrolling is a very good option. Like, in any situation, I would never go willy-nilly fetching as much data as is there. I would figure out how to minimize the amount of data as much as possible. In this example, we talked about movies. All the metadata that's not shown for each movie, none of that should be fetched until the user is going to see it. So not until they click into movie 32. Or modal is going to pop up with that exact information.
JASON: Right, right, right. This is great. So let's talk a little bit about for folks who aren't going to roll their own, what are the trade-offs here? So you mentioned one thing. If you're just rolling your own, it's lighter weight. You're not bringing an external library. Why would you bring an external library? What extra bells and whistles do we get?
SHAUNDAI: So React Virtualized is a way out of the box. It comes with different props and things that just make it easy for you to plug things in. I'm a big fan of using libraries if they're there. I know Brian was work on the React team, so I trust -- there are certain people that when they build something, and I'm like, okay, this is going to be pretty solid. I know it's going to work well along with React. So yeah, I would reach for it if you don't want to spend the time to have to learn to implement something yourself. Like, if you want something that's just kind of, you know, right out of the box. Examples that I've seen of people not reaching for a library is if you are averse to the idea of importing libraries for things that you can build yourself because you want to keep your code as lightweight or have control over it. Or if you have different edge cases. I think I saw a post recently about somebody who -- I can't remember exactly why, but their scenario for using virtualization would have just been too much work to have to customize React Virtualize. So they were like, I'll just build it myself. So yeah, the main trade-off, though, is just any library you add in is added code. So there's more stuff that your browser has to parse by way of that. If you're doing tree shaking, you're using Babel or Webpack, which I think most modern React apps these days are using something like that. Then it's going to shake out a lot of that code. But you are still adding in additional code.
JASON: Here's another option if you want to check that one out. So from my perspective, what I'm always looking at with build versus buy or build versus import in the case of third-party libraries is every line of code you write is a line of code you have to maintain. So the major trade-off is, sure, it can be fun to write the 150 lines of code that do really clever and performant virtualization, but if you don't need that, if you're not hitting an edge case that wouldn't be served by, you know, Tanstack or Brian's library, then why opt into the maintenance? Why opt into the additional context and the explaining of this tool and the lack of documentation and all of the things that happen when you're not using something out there. The nice thing about using a tool like this is, look, all the docs are already written for you. You can hand this to your team and they just go. I'm going to bet money that Brian did the same thing, where it's got, you know, full -- yeah, here you go. Here's how to do it. Here's the documentation. You can go open issues, and there's a whole bunch of people talking about this stuff and maintaining it. So if you don't need the edge case, if you're not hitting something that's not served and this library is not giant, which I don't know what the sizes are of this library or the Tanstack virtual, but my guess is they're not super big. So the trade-off is very much like are you really -- do you really want to own this? Do you want this to be a little tiny product you have to maintain internally? Because that's not nothing in terms of work.
SHAUNDAI: Agreed, yeah. Think about scalability. If another teammate comes by and asks you how do you do that, you could just say, hey, use React Virtualize, here's the docs. Or you can give them a whole rundown on how you built it and try to remember all the stuff you put into it and the weird edge cases and things. So, yeah, I'm a big fan of leveraging open source, other people's trusted work.
JASON: And the thing is, every single company 100% will have edge cases where you need custom stuff. That's why internal tooling teams exist. So don't overburden your internal tooling team by opting into creating things that don't need to be re-created because they're going to need that brain power for all the little fiddly crap that's unique to your company.
SHAUNDAI: True. (Laughter)
JASON: But anyway, yeah, I think this is wonderful. I did see there were a couple questions in here that I just want to address. Mr. Nike Guy asked how does Chrome know the max height of the real movie list. It's because we're specifically calculating it here. We do the inner height, and then we set it on the inner box here. That way the box is always the size of the full list, and then we're just rendering in the items as they're scrolled into the view port. Let's see. I don't see any other questions, but I do see Henri. What's up, Henri? Shaundai, why should people go if they want to learn more about virtualization or more about you?
SHAUNDAI: For virtualization, I would check out these libraries. I would look up -- so when you look up virtualization, look up windowing. There's a lot more resources that come out on that.
JASON: Like React windowing?
SHAUNDAI: Yes, yeah. Googling is such a tough thing. Yes, perfect. Optimizing performance, that is a great article. And then there's a Medium article that I had found.
JASON: And we also have a question. I think this one came from Twitter. Would you be able to recommend the tools you use to profile a Next.js app? How would you approach an existing web app to optimize performance with a focus on server-rendered end points? I feel like that's one we saw on Twitter earlier today.
SHAUNDAI: Yes. Preach.
JASON: With that, I think we're about out of time. I feel like I jumped in as you were about to talk about another resource. I wanted to make sure I didn't cut that off.
SHAUNDAI: I had a Medium article. I was listening to you and I forgot to find it.
JASON: No worries.
SHAUNDAI: I'll tweet it out. I think they have enough resources. I was going to say, find me on Twitter. I'm still using Twitter. If I decide not to, I will let you know on Twitter that I'm moving on.
JASON: Oh, and we got a link tree, everyone. That's what you really need. That's the jam. Get on that link tree and just click every one of those links. All right. With that, I'm going to do another shout out to our sponsors. This episode has been live captioned. We have had Rachel with us today. Thank you for being here. That's through White Coat Captioning. Thank you, thank you, thank you. That's made possible through the support of our sponsors, Netlify, Nx, and New Relic, who has a ghost logo. Thank you all very, very much for that. While you're checking out things on the site, check out that schedule. We have so much good stuff coming up. I cannot wait to do the rest of this. It's going to be -- we're going to close out the year strong, y'all. Just get ready. And there's more here that I haven't put on the site yet. So just buckle up. Follow on Twitch. Subscribe on YouTube. You can add this Google calendar to get the details. Any number of ways, you can get involved. I want you to hang out. I'd like to be friends. Shaundai, it's been great hanging out with you. Thank you so much for spending some time with us today.
SHAUNDAI: Thank you for having me. As always, it was fun.
JASON: Awesome. All right, y'all. We're going to go and raid -- let's see. Martin is up and running. Let's go see what Martin is up to. Thank you all so much. We will see you next time.
Closed captioning and more are made possible by our sponsors: