Reusing lifecycle functions in Svelte components
In the previous section, we learned that we can extract the calling of lifecycle functions into a function and reuse the function in other components.
Let’s look at an example. In this example, after the component is added to the screen for 5 seconds, it will call the showPopup
function. I want to reuse this logic of calling showPopup
in other components:
<script> import { onMount } from 'svelte'; import { showPopup } from './popup'; onMount(() => { const timeoutId = setTimeout(() => { showPopup(); }, 5000); return () => clearTimeout(timeoutId); }); </script>
Here, I can extract the logic into a function, showPopupOnMount
:
// popup-on-mount.js import { onMount } from 'svelte'; import { showPopup } from './popup'; export function showPopupOnMount() { onMount(() => { const timeoutId = setTimeout(() => { showPopup(); }, 5000); return () => clearTimeout(timeoutId); }); }
And now, I can import this function and reuse it in any component:
<script> import { showPopupOnMount } from './popup-on-mount'; showPopupOnMount(); </script>
You may be wondering, why not only extract the callback function and reuse that instead?
// popup-on-mount.js import { showPopup } from './popup'; export function showPopupOnMount() { const timeoutId = setTimeout(() => { showPopup(); }, 5000); return () => clearTimeout(timeoutId); }
Over here, we extract only setTimeout
and clearTimeout
logic into showPopupOnMount
, and pass the function into onMount
:
<script> import { onMount } from 'svelte'; import { showPopupOnMount } from './popup-on-mount'; onMount(showPopupOnMount); </script>
In my opinion, the second approach of refactoring and reusing is not as good as the first approach. There are a few pros in extracting the entire calling of the lifecycle functions into a function, as it allows you to do much more than you can otherwise:
- You can pass in different input parameters to your lifecycle functions.
Let’s say you wish to allow different components to customize the duration before showing the popup. It is much easier to pass that in this way:
<script> import { showPopupOnMount } from './popup-on-mount'; showPopupOnMount(2000); // change it to 2s </script>
- You can return values from the function.
Let’s say you want to return the
timeoutId
used in theonMount
function so that you can cancel it if the user clicks on any button within the component.It is near impossible to do so if you just reuse the callback function, as the value returned from the callback function will be used to register for the
onDestroy
lifecycle function:<script> import { showPopupOnMount } from './popup-on-mount'; const timeoutId = showPopupOnMount(2000); </script> <button on:click={() => clearTimeout(timeoutId)} />
See how easy it is to implement it to return anything if we write it this way:
// popup-on-mount.js export function showPopupOnMount(duration) { let timeoutId; onMount(() => { timeoutId = setTimeout(() => { showPopup(); }, duration ?? 5000); return () => clearTimeout(timeoutId); }); return timeoutId; }
- You can encapsulate more logic along with the lifecycle functions.
Sometimes, the code in your lifecycle functions callback function does not work in a silo; it interacts with and modifies other variables. To reuse lifecycle functions like this, you must encapsulate those variables and logic into a reusable function.
To illustrate this, let’s look at a new example.
Here, I have a counter that starts counting when a component is added to the screen:
<script> import { onMount } from 'svelte'; let counter = 0; onMount(() => { const intervalId = setInterval(() => counter++, 1000); return () => clearInterval(intervalId); }); </script> <span>{counter}</span>
The
counter
variable is coupled with theonMount
lifecycle functions; to reuse this logic, thecounter
variable and theonMount
function should be extracted together into a reusable function:import { writable } from 'svelte/store'; import { onMount } from 'svelte'; export function startCounterOnMount() { const counter = writable(0); onMount(() => { const intervalId = setInterval(() => counter.update($counter => $counter + 1), 1000); return () => clearInterval(intervalId); }); return counter; }
In this example, we use a
writable
Svelte store to make thecounter
variable reactive. We will delve more into Svelte stores in Part 3 of this book.For now, all you need to understand is that a Svelte store allows Svelte to track changes in a variable across modules, and you can subscribe to and retrieve the value of the store by prefixing a
$
in front of a Svelte store variable. For example, if you have a Svelte store namedcounter
, then to get the value of the Svelte store, you would need to use the$
counter
variable.Now, we can use the
startCounterOnMount
function in any Svelte component:<script> import { startCounterOnMount } from './counter'; const counter = startCounterOnMount(); </script> <span>{$counter}</span>
I hope I’ve convinced you about the pros of extracting the calling of lifecycle functions into a function. Let’s try it out in an example.
Exercise 1 – Update counter
In the following example code, I want to know how many times the component has gone through the update cycle.
Using the fact that every time the component goes through the update cycle, the afterUpdate
callback function will be called, I created a counter that will be incremented every time the afterUpdate
callback function is called.
To help us measure only the update count of a certain user operation, we have functions to start measuring and stop measuring, so the update counter is only incremented when we are measuring:
<script> import { afterUpdate } from 'svelte'; let updateCount = 0; let measuring = false; afterUpdate(() => { if (measuring) { updateCount ++; } }); function startMeasuring() { updateCount = 0; measuring = true; } function stopMeasuring() { measuring = false; } </script> <button on:click={startMeasuring}>Measure</button> <button on:click={stopMeasuring}>Stop</button> <span>Updated {updateCount} times</span>
To reuse all the logic of the counter:
– the counting of update cycles and the starting and stopping of the measurement – we should move all of it into a function, which ends up looking like this:
<script> import { createUpdateCounter } from './update-counter'; const { updateCount, startMeasuring, stopMeasuring } = createUpdateCounter(); </script> <button on:click={startMeasuring}>Measure</button> <button on:click={stopMeasuring}>Stop</button> <span>Updated {$updateCount} times</span>
The update counter returns an object that contains the updateCount
variable and the startMeasuring
and stopMeasuring
functions.
The implementation of the createUpdateCounter
function is left as an exercise to you, and you can check the answer at https://github.com/PacktPublishing/Real-World-Svelte/tree/main/Chapter01/01-update-counter.
We’ve learned how to extract a lifecycle function and reuse it, so let’s take it up a notch and reuse multiple lifecycle functions in the next pattern: composing lifecycle functions.