8 Lifecycle functions

This chapters covers

  • onMount to run code when a component is added to the DOM
  • beforeUpdate to run code before every component update
  • afterUpdate to run code after every component update
  • onDestroy to run code when a component is removed from the DOM

In some applications there are actions that need to be performed when a component is added to or removed from the DOM. There are also situations where actions need to be performed before or after a component is updated. Svelte supports this by allowing the registration of functions to be invoked when four specific events occur in the lifecycle of a component instance:

  • When it is mounted (added to the DOM)

  • Before it is updated

  • After it is updated

  • When it is destroyed (removed from the DOM)

A component is “updated” if any of its props change or any of its state variables change. Recall that state variables are top-level variables in a component that are used in its HTML.

8.1 Setup

To register functions for these events, import the provided lifecycle functions from the svelte package:

import {afterUpdate, beforeUpdate, onDestroy, onMount} from 'svelte';

Call these functions, passing them a function to be called when the event occurs. They must be called during component initialization. This means they cannot be called conditionally or be called in a function that is not invoked before each component instance is mounted.

Listings 8.1 and 8.2 show examples of using each of these events. Enter the code in these listings into a REPL, and open the DevTools console (or expand the Console pane in the REPL). Click the Show checkbox and the Demo button to see when each of the lifecycle functions is called.

Listing 8.2 Demo component that uses all the lifecycle functions

<script>
  import {onMount, beforeUpdate, afterUpdate, onDestroy} from 'svelte';

  let color = 'red';

  function toggleColor() {
    color = color === 'red' ? 'blue' : 'red';       
  }

  onMount(() => console.log('mounted'));
  beforeUpdate(() => console.log('before update'));
  afterUpdate(() => console.log('after update'));
  onDestroy(() => console.log('destroyed'));
</script>
 
<button on:click={toggleColor} style="color: {color}">
  Demo
</button>

Changing the color triggers the beforeUpdate function, updates the color of the button, and triggers the afterUpdate function.

Listing 8.2 App that uses the Demo component

<script>
  import Demo from './Demo.svelte';
  let show = false;                      
</script>

<label>
  <input type="checkbox" bind:checked={show}>
  Show
</label>
{#if show}
  <Demo />
{/if}

This determines whether the Demo component should be rendered. Changing it causes the Demo component to be mounted and unmounted. The beforeUpdate function is called before the onMount function, and the afterUpdate function is called after the onMount function.

The lifecycle functions can be called any number of times. For example, if onMount is called three times, passing it three functions, all of them will be called when the component is mounted, in the order in which they were registered.

Note that functions passed to beforeUpdate are called before functions passed to onMount. This happens because component props and state are evaluated before a component is added to the DOM.

8.2 The onMount lifecycle function

The most commonly used lifecycle function is onMount.

One use is to anticipate where a user is most likely to want to enter data in a form that is rendered by a component. An onMount function can move focus to that input when the component is first rendered. The following section shows an example of this.

Another use is to retrieve data needed by a component from API services. For example, a component that displays information about employees at a company can use an onMount function to obtain the data and store it in a top-level variable so it can be rendered. Section 8.2.2 shows an example of this.

8.2.1 Moving focus

Here’s an example that moves the focus so the user is immediately ready to enter their name without having to click in the input or press the Tab key.

Note Consider the impact on accessibility when moving focus. This can cause screen readers to skip over content before the input.

<script>
  import {onMount} from 'svelte';
  let name = '';
  let nameInput;
  onMount(() => nameInput.focus());
</script>
 
<input bind:this={nameInput} bind:value={name}>

The bind:this directive sets a variable to a reference to a DOM element. In the preceding example, the variable nameInput is set to the DOM element of the HTML input. This is used in the function passed to onMount to move the focus to the input.

Recall that in section 7.2 you saw an even easier way to move the focus--by using an action.

8.2.2 Retrieving data from an API service

Here is an example that retrieves data about employees at a company from an API service when the component is mounted. This is the same API service described in section 4.3. The array of employee objects returned is sorted by their names (first name before last name) and stored in a top-level variable. The component renders the employee data in a table (see figure 8.1).

Figure 8.1 Employee table

Listing 8.3 App that uses onMount to fetch data

<script>
  import {onMount} from 'svelte';
 
  let employees = [];
  let message;
 
  onMount(async () => {
    const res = await fetch(                                 
      'http://dummy.restapiexample.com/api/v1/employees');
    const json = await res.json();
    if (json.status === 'success') {
      employees = json.data.sort(                            
        (e1, e2) => e1.employee_name.localeCompare(e2.employee_name));
      message = '';
    } else {
      employees = [];
      message = json.status;
    }
  });
</script>
 
<table>
  <caption>Employees </caption>
  <tr><th>Name</th><th>Age</th></tr>
  {#each employees as employee}
    <tr>
      <td>{employee.employee_name}</td>
      <td>{employee.employee_age}</td>
    </tr>
  {/each}
</table>
{#if message}
  <div class="error">Failed to retrieve employees: {message}</div>
{/if}
 
<style>
  caption {
    font-size: 18px;
    font-weight: bold;
    margin-bottom: 0.5rem;
  }
  .error {
    color: red;
  }
  table {
    border-collapse: collapse;
  }
  td, th {
    border: solid lightgray 1px;
    padding: 0.5rem;
  }
</style>

For details on using the browser-provided Fetch API, see appendix B.

This sorts the employees on their name, first before last.

8.3 The onDestroy lifecycle function

To register a function to be called when a component is removed from the DOM, pass the function to onDestroy. This is typically used for cleanup operations, such as clearing timers (created with setTimeout) or intervals (created with setInterval). It can also be used to unsubscribe from stores where auto-subscribe ($ syntax) is not used.

For example, suppose we want to cycle through a set of colors for some text, changing the color every half second. Here is a Svelte component that does this.

Listing 8.4 ColorCycle component in src/ColorCycle.svelte

<script>
  import {onDestroy, onMount} from 'svelte';
  export let text;
  const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'];
  let colorIndex = 0;
  let token;
 
  onMount(() => {
    token = setInterval(() => {
      colorIndex = (colorIndex + 1) % colors.length;
    }, 500);
  });
 
  onDestroy(() => {
    console.log('ColorCycle destroyed');
    clearInterval(token);
  });
</script>
 
<h1 style="color: {colors[colorIndex]}">{text}</h1>

The following app component uses the ColorCycle component. It provides a way to remove the ColorCycle component from the DOM and add a new instance.

Listing 8.5 App that uses the ColorCycle component

<script>
  import ColorCycle from './ColorCycle.svelte';
  let show = true;
</script>

<button on:click={() => show = !show}>Toggle</button>

{#if show}
  <ColorCycle text="Some Title" />
{/if}

An alternative to using onDestroy is to return a function from the function registered with onMount. This function will be called when the component is removed from the DOM.

Note This approach is a bit like the useEffect hook in React, but it differs in that functions passed to useEffect are run on both mount and updates.

Here is the ColorCycle component implemented with this approach.

Listing 8.6 ColorCycle component with onMount returning a function

<script>
  import {onMount} from 'svelte';
  export let text;

  const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'];
  let colorIndex = 0;

  onMount(() => {
    const token = setInterval(() => {
      colorIndex = (colorIndex + 1) % colors.length;
    }, 500);
    return () => clearInterval(token);
  });
</script>
 
<h1 style="color: {colors[colorIndex]}">{text}</h1>

One advantage of this approach is that the token variable can be scoped to the function passed to onMount instead of being a top-level variable in the component. This visually groups the setup and cleanup code, making it easier to follow.

8.4 The beforeUpdate lifecycle function

To register a function to be called before each component update, pass the function to beforeUpdate. The beforeUpdate function is rarely used.

One reason to use it is to capture part of the DOM state before it is updated by Svelte so those values can be restored after the update using the afterUpdate function. For example, the cursor position in an input can be captured and restored after its value is changed.

The following component does this when the UPPER button is clicked. This changes the value of the input to be all uppercase. Figures 8.2 and 8.3 show the component before and after the UPPER button is clicked. The cursor position, including the range of characters selected, is restored after the change.


Figure 8.2 Before clicking UPPER with “fine” selected

Figure 8.3 After clicking UPPER with “FINE” still selected

Listing 8.7 App that uses beforeUpdate and afterUpdate

<script>
  import {afterUpdate, beforeUpdate} from 'svelte';

  let input, name, selectionEnd, selectionStart;

  beforeUpdate(() => {
    if (input) ({selectionStart, selectionEnd} = input);          
  })

  afterUpdate(() => {
    input.setSelectionRange(selectionStart, selectionEnd);
    input.focus();
  });
</script>

<input bind:this={input} bind:value={name}>                       
<button on:click={() => name = name.toUpperCase()}>UPPER</button>

This uses destructuring to get two properties from the DOM input object.

Recall that bind:this captures the associated DOM element.

8.5 The afterUpdate lifecycle function

To register a function to be called after each component update, pass the function to afterUpdate. This is typically used to perform additional DOM updates after Svelte has updated the DOM.

The previous example already used afterUpdate, but used it in conjunction with beforeUpdate. The following Svelte app provides another example. It allows the user to enter items they want for their birthday. The list displays at most three items, and new items are added at the end (see figure 8.4). After each item is added, we want to automatically scroll to the bottom so the most recently added items are visible.

The function passed to afterUpdate scrolls the list to the bottom.

Figure 8.4 Birthday list

Listing 8.8 App that uses afterUpdate

<script>
  import {afterUpdate} from 'svelte';
  let input;
  let item = '';
  let items = [];
  let list;
 
  afterUpdate(() => list.scrollTo(0, list.scrollHeight));
 
  function addItem() {       
    items.push(item);
    items = items;           
    item = '';               
    input.focus();           
  }
</script>
 
<style>
  .list {
    border: solid gray 2px;
    height: 52px;            
    overflow-y: scroll;
    padding: 5px;
  }
</style>
 
<p>Tell me what you want for your birthday.</p>
 
<form on:submit|preventDefault>
  <input bind:this={input} bind:value={item}>
  <button on:click={addItem}>Add</button>
</form>
 
<div class="list" bind:this={list}>
  {#each items as item}
    <div>{item}</div>
  {/each}
</div>

This function has access to item, which is a state variable.

This triggers an update.

This clears the input.

This prepares the user for entering another item.

This is enough height to display three items.

8.6 Using helper functions

Lifecycle functions can be called from helper functions whose purpose is to implement lifecycle functionality that can be shared between multiple components. These helper functions are best defined in separate .js files, which allows them to be imported and used by more than one component. This is similar to defining custom React hooks.

It is recommended that you name these helper functions starting with “on”, similar to how React hook names start with “use”.

For example, we can implement lifecycle helper functions that move the focus to the current first input and log when a component is mounted.

Listing 8.9 Helper functions defined in src/helper.js

import {onMount} from 'svelte';
 
export function onMountFocus() {
  onMount(() => {
    const input = document.querySelector('input');      
    input.focus();
  });
}
 
export function onMountLog(name) {
  onMount(() => console.log(name, 'mounted'));
}

This finds the first input element.

Let’s create two components that use these helper functions. The NameEntry component allows the user to enter the name of a person (figure 8.5 and listing 8.10). The AgeEntry component allows their age to be entered (figure 8.6 and listing 8.11). These components use both of the helper functions defined in listing 8.9.


Figure 8.5 NameEntry component

Figure 8.6 AgeEntry component

The component that renders NameEntry and AgeEntry toggles between showing one or the other based on the value of a checkbox with the label “Enter Age?” (see listing 8.12).

Listing 8.10 NameEntry component in src/NameEntry.svelte

<script>
  import {onMountFocus, onMountLog} from './helper';
  export let name;
  onMountLog('NameEntry');
  onMountFocus();
</script>
 
<label>
  Name
  <input bind:value={name}>
</label>

Listing 8.11 AgeEntry component in src/AgeEntry.svelte

<script>
  import {onMountFocus, onMountLog} from './helper';
  export let age;
  onMountLog('AgeEntry');
  onMountFocus();
</script>
 
<label>
  Age
  <input type="number" min="0" bind:value={age}>
</label>

The following listing shows a component that renders AgeEntry or NameEntry.

Listing 8.12 App that uses AgeEntry and NameEntry

<script>
  import {onMountLog} from './helper';
  import AgeEntry from './AgeEntry.svelte';
  import NameEntry from './NameEntry.svelte';
 
  let age = 0;
  let enterAge = false;
  let name = '';
  onMountLog('App');
</script>
 
{#if enterAge}
  <AgeEntry bind:age />       
{:else}
  <NameEntry bind:name />     
{/if}
 
<label>
  Enter Age?
  <input type="checkbox" bind:checked={enterAge}>
</label>
 
<div>{name} is {age} years old.</div>

This uses bind to get age from AgeEntry.

This uses bind to get name from NameEntry.

8.7 Building the Travel Packing app

The only place where the Travel Packing app needs to use a lifecycle function is in Dialog.svelte. This registers the dialog polyfill with each dialog instance after it is mounted. (The Dialog component and uses of it were added to the Travel Packing app in chapter 7.)

The dialog polyfill was registered with the following line of code:

onMount(() => dialogPolyfill.registerDialog(dialog));

The dialog variable was set using bind:this with code like the following:

<Dialog title="some-title" bind:dialog>

In the next chapter you will learn how to implement routing between the “pages” of an app.

Summary

  • Components can register functions to be called at specific points in their lifecycle.

  • The onMount function registers a function to be called when each component instance is added to the DOM.

  • The onDestroy function registers a function to be called when each component instance is removed from the DOM.

  • The beforeUpdate function registers a function to be called before each component instance is updated due to receiving a new prop value or having a state change.

  • The afterUpdate function registers a function to be called after each component instance is updated due to receiving a new prop value or having a state change.

  • The functions passed to lifecycle functions can be defined outside component source files in plain JavaScript files. This allows them to be shared by multiple components and by other JavaScript functions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.74.54