Transcript from the "Lifecycle" Lesson
>> Okay, it's time to talk about the circle of life. So every component has a lifecycle that starts when it's created and ends when it is destroyed. And there's a handful of functions that allow you to run code inside your component at key moments during that lifecycle. The one that you're gonna use most frequently is called onMount.
[00:00:18] And is a function that allows you to specify a callback that will get invoked when the component is first mounted to the DOM. So in this exercise, we have a canvas element that we would like to animate using the paint function in this gradient js file. It's just gonna take an input canvas context and a value t, and it's gonna do some maths, and it's gonna turn that into a colorful canvas.
[00:00:42] So we'll first import onMount into our component. And then we'll call onMount, pass it a callback. And first up, we're gonna get a reference to that canvas element using document query selector. And if alarm bells are ringing that we shouldn't be using document.query selector, you're absolutely right, but we're gonna come to that in a later exercise.
[00:01:16] Begin by doing canvas =, document.querySelector('canvas'). And then if you haven't used the Canvas API before, once you've got your reference to the element, you then need to create a Canvas context that you can paint with, const context = canvas.getContext('2D'). 2D as opposed to WebGL. And then we're gonna do some work in a loop.
[00:01:47] Gonna call requestAnimationFrame. And inside here, we're gonna have a name function so that we can refer to it that takes a value t, which is the current time. And inside the loop, the first thing we're gonna do is call the loop again inside another requestAnimationFrame. And then we're gonna call our paint function, using the context that we just created with the t value that got passed into the requestAnimationFrame handler.
[00:02:27] So now, this function is being called roughly 60 times a second. Okay, there is a problem with the code that we just wrote. And this is kind of an easy thing to overlook, but it's important not to make this mistake. If you're doing some work in OnMount, you need to make sure that you undo it when the component is unmounted.
[00:02:49] And the way that we're gonna do that is by returning a function that calls some cleanup code. Any function that is returned from OnMount will get called when the component is destroyed. In our case, we wanna cancel the animation frame to stop this loop from running. In order to do that, we need to create a frame variable locally.
[00:03:11] And we need to assign to that every time we call request animation frame. And then in the return callback, I'll just call cancelAnimationFrame and pass that ID in. And so now when this component is taken off the screen, we'll stop running that loop. Now, as you saw, we had that code running, and it continued to run, and it was actually kind of fine.
[00:03:38] It was just updating a canvas that you couldn't see anymore, which is obviously no use. If you did that too many times in the same app, you would eventually start to run into memory leaks and that's something that we very much want to avoid. We have other life cycle functions, beforeUpdate and afterUpdate.
[00:03:59] Lifecycle functions that are called immediately before the DOM is updated and immediately after the DOM is updated. So basically, in Svelte, we're dealing with declarative state. But occasionally, we need to break out of that. We need to use an escape hatch to access the DOM directly because some things are hard to do in a purely state driven way.
[00:04:23] So this is the Eliza Chatbot. Before there was ChatGPT, there was Eliza. This comes from the 70's and it's pretty cool. No, actually, everything is great. We can talk to it. The only problem is the messages aren't appearing because we've got to scroll. Completely misunderstood me. No, I'm very positive.
[00:04:53] Right, [LAUGH] you are being a bit negative. But we have to do that every time we add some more content because the DOM doesn't know that it needs to scroll. But we can fix that using beforeUpdate and afterUpdate. We'll begin by creating an 'autoscroll' value. This is gonna track whether or not we currently should be in autoscroll mode.
[00:05:20] I'm gonna default to false. Now, the first time beforeUpdate gets called, the div might not have been rendered to the DOM yet. So we're getting a reference to the development using something called bind this which we'll discover in a later exercise. For now, just pretend that this makes sense.
[00:05:52] So if the div has been created, then we wanna calculate whether or not we should be in auto scroll mode. And the way that we're gonna do that is by figuring out what the distances between the bottom of the scroll area and the outer container. And it took me a few goes to get this right.
[00:06:18] This is the sort of thing that I struggle with. So I'm just gonna copy the code over from the left, const scrollableDistance=div.scrollHeight-div.offsetHeight. And then we're gonna auto scroll if there's less than 20 pixels between the bottom of the window and the bottom of the container. And then once we've updated the DOM, once the new chat messages have been added to the window, if we are in auto scroll mode.
[00:07:08] Then in that case, we're gonna call div.scrollTo(0). And then div.scrollHeight will take us to the very bottom of the chat. All right, let's see if that worked. I don't have a problem. What is your problem? And you'll see that it's auto scrolling nicely. If, on the other hand, we were not near the bottom of the chat, Then autoscroll would be false.
[00:07:49] And so when the afterUpdate function gets invoked, we're not gonna scroll back to the bottom of the chat. All right, you probably won't find yourself using beforeUpdate and afterUpdate all that often. It's really only when you need to interact with the DOM in this very specific way. But it's useful when you do need it.
[00:08:16] Another lifecycle function that we're gonna talk about is tick. Now, onMount, beforeUpdate, and afterUpdate all have to get called while the component is initializing. It doesn't have to be inside the script tag. You could be calling out to a function in another module, but it does have to get called while the component is initializing because that's how the callbacks get assigned to that component.
[00:08:39] Tick is different. Tick can get called at any point. And what tick does is it returns a promise that resolves when any pending state updates are applied to the DOM. The reason this works is that when you update component state in Svelte, it doesn't update the DOM immediately.
[00:08:59] So when we do count plus equals 1, it's not updating the text inside the button right there and then. It's actually waiting to see if there are any more state updates that it's gonna need to apply at the same time. Because it's more efficient to do things in a batched way than it is to do them synchronously.
[00:09:18] But that can be tricky if you're updating state in such a way that causes the DOM to update in a way that you need to monitor. For example, a little bit of a contrived example but we've got a text area here. And if I select some text like this, and I hit the tab key, then we have a handle keydown function which is gonna get the selection range.
[00:09:46] And it's gonna turn it to shouty case or quiet case depending on what it currently is. And so if I select that and I hit tab, it's done that. It's turned that text into uppercase text, but it's also moved my cursor to the end of the text area, which is not what I want.
[00:10:05] I want the selection to stay in place. And I have some code here that should be doing that. It's resetting the selection start and the selection end. But it's not having any effect because the DOM hasn't updated yet. We're waiting to see if there are any other status updates that we need to take accounts of.
[00:10:23] So we can fix this by importing the tick helper from Svelte. And then immediately before we call this.selectionStart = selectionStart, we're just gonna await that tick. And so now if we select some text and we hit tab, it will uppercase it but it will preserve the selection.
[00:10:52] If I hit it again, it will lowercase it. Again, this isn't a function that you will use a whole lot. In the same way that beforeUpdate and afterUpdate are really kind of escape hatches, tick is an escape hatch for when you need to interact with the DOM manually.