Refactoring the Infinite Scroll Feature to Fix Memory Leak
In this guide we are going to take care of a subtle, but important bug with our infinite scroll feature.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

It might be working properly, however, we have created what is called a memory leak and so in this guide, we're gonna see what it is and also how to fix it and hopefully, when this has been completed, you'll have a better idea on how to use some of the component lifecycle hooks. I know we've worked with them, we've had a deep dive on them, but this is a perfect example of why they're needed.

This is a very practical case study and so hopefully that will kinda reinforce why we want to use them and when we're gonna use them and it's a reason why I actually structured the initial code the way I did, to kinda create an example of when they would be used in real life application.

So let's first see what the bug is. If you come to the blog page and hit refresh, you'll see if you kept your debugging output, it says getting and it brings in the items. Now don't scroll down to the bottom because this is like I said, this is a subtle bug.

It does not happen in all use cases and so if you go all the way down and you view all of the records, this bug is not going to appear, it's still gonna be behind the scenes, but it's not going to appear. So if you just load the page and then say go to the homepage or the portfolio manager, one of the taller pages and let that page load.

And watch what happens in the console if I scroll all the way down. I'm gonna scroll all the way down and uh oh, we have a big warning and then we have something very confusing, we actually went out and grabbed our portfolio blogs again.

large

So what's happening here is that because we're attaching the window... Or because we're attaching the onscroll event to the window that means the browser, that means the entire browser session, when the user left that component, when they left the blog page, that listener was still there.

So what happened is it went and it called to get new blogs even though we're on the homepage, that's definitely not what we're wanting. The entire purpose on component-driven architecture is to be able to encapsulate all of your functionality into these separate components and they shouldn't have overlap. So you shouldn't have a listener on one component affecting some other page unless you're trying to do it purposefully and then you have to be very careful of that.

But for our example, this is definitely a bug so we're gonna walk through how we can fix it and along the way you're also going to learn a different way of implementing an event listener. So let's switch over to the code and as you can see here we have our normal setup for the blog component.

What we're going to do is we're going to take out the listener, this onscroll listener from the activateInfiniteScroll function. We're gonna pull it out and so we're going to make this function a little bit more simple, it's not going to have the listener inside of it.

Instead, we're going to create the listener in the constructor because that's when we wanna call it anyway. That's the whole purpose of why we're calling the this.activateInfiniteScroll. And then because we're able to place that in the constructor what we can do is then we're going to use a lifecycle hook for when that component goes away or it unmounts.

We're going to be able to simply remove that listener and it makes it much easier to add and remove listeners that way as opposed to when you embed them in the functions. So first and foremost I'm actually gonna rename this function because it's no longer activating the infinite scroll, we're simply saying that on scroll we want these items to happen.

And so what you can do is inside of this function, starting for me on line 23 where it says window.onscroll, just get rid of that entire line and then come down to the very bottom right here and get rid of that code. And you can hit save and now you can see that our onScroll function is more simple.

blog.js

onScroll() {
  if ( 
    this.state.isLoading ||
    this.state.blogItems.length === this.state.totalCount
  ) {
    return;
  }

  if (
    window.innerHeight + document.documentElement.scrollTop ===
    document.documentElement.offsetHeight
  ) {
    this.getBlogItems();
  }
}

It no longer has that listener, it simply has the conditional to check if the state's loading, if the blog item's length is the same as the total count and then it also has the window.innerHeight and all of the other elements for getting the data. This is more based around simply the infinite scroll feature and less on the listener now.

So if you come up here, we can get rid of this activateInfiniteScroll feature. We can bind onScroll to the component so I'll say this.onScroll equals this.onScroll.bind(this) and now we can add the listener so this the same as when we were... or this will create the same effect as when we called activateInfiniteScroll, but now it's going to take the listener part and put it inside of the constructor.

So the way that you can do that is we're going to say window and then from there, we'll just say window.addEventListener and the type of listener you can see with, this is one of the few times that the recommendations can actually be helpful, it shows you all of the different options that you have.

large

I want to inside of a string say that I want the scroll listener and then the way that the event listener works is the very first command or the first argument in the function is going to be the type of listener you're adding. The second one is saying what function do you want to tie in with this.

So when you are calling or when you are building this infinite scroll feature, what exactly are you wanting the behavior to be like and we know we want it to be with this inner scroll feature or function and you have to say this.onScroll and then there's one other command here and I'm gonna say false. And if you're curious on what this represents, this is a set of potential optional items and I'll show you what this represents here, I have the documentation open.

And you can see that we have the syntax is the target... for us it's window. Remove, or this is the removeEventListener one, but the same syntax is the same for adding and removing so it's either add or removeEventListener, the type so that's why we said scroll and then the listener which is for us our function and then a set of options.

large

And so if you're interested in reading more about that, I'll include a link in the show notes, but for right now this is how we can't tie in that addEventListener.

blog.js

window.addEventListener("scroll", this.onScroll, false);

So I'm gonna hit save and let's just test this out. Let's see if we have a feature that's actually working before we talk about how we can remove this when the component goes away. So let's clear everything out of the console, I'm gonna hit refresh here and you can see it went out and it got the portfolio blog items and if I scroll down you can see yes, our infinite scroll feature is still working.

large

So we've migrated our listener from inside of a function into the constructor so that we can have a little more control over it, and now let's talk about how we can get rid of it. So I'm going to find our componentWillMount and the only reason I'm putting it here, you could put it anywhere else in the class, but I'm putting it here just because I like to keep my lifecycle hooks together, it just makes it a little easier to keep track of them.

So I'm gonna say componentWillUnmount, and so what this means is that when the component goes away, anything I place in here, any code, functions, anything like that, is going to be triggered. And in our case, I simply wanna say window.removeEventListener. I want to remove the scroll event and the function that it's tied to is this.onScroll and then I want false for that third argument.

blog.js

componentWillMount() {
  this.getBlogItems();
}

componentWillUnmount() {
  window.removeEventListener("scroll", this.onScroll, false);
}

So let's hit save and now let's see if our bug has been fixed. So I'm gonna come to Chrome, make sure you hit refresh just to make sure you're working with the latest version of the code. And I'm not gonna go all the way down, I'm just going to work with these 10 items. If I go to the homepage and let everything load up, if I scroll down now, you can see that bug is fixed.

It's no longer trying to call getBlogs because as soon as that component was unmounted so when we left the blog page, then that event listener was also removed, you can go test it out on any of the other pages as well. Portfolio manager no longer has it either.

Let's walk through this very quickly, in this guide we migrated our event listener from inside of a function into the constructor. We used a slightly different syntax for adding the listener, we walked through the syntax for that and then from there we tied all of that in with a lifecycle hook that would be triggered whenever that component was taken away.

And this goes to a very critical aspect of React development, really any development, but specifically with React, you wanna make sure that you don't have any types of leaks or weird side effects whenever you're building out your system. So it's important to think if you have done something like create a listener. Whenever you're building that feature in, it's important for you to think through how that ties in with the rest of the application.

So if you add a listener, make sure that that doesn't go and affect other parts or other components of that app or else you're gonna end up working with some very weird bugs. This one was pretty subtle and most likely no one would even notice, but I have seen other situations where the entire app would crash under certain circumstances and it all had to do with the memory leak.

So whenever you're working with event listeners, make sure that you're thinking through the way they're working and also leverage the lifecycle hook so you do have the ability to clear off any kind of memory usage that other components aren't going to utilize.

Resources