Conditionals and Loops

In this module, we will go over how to express logic in our HTML using conditionals and loops.
Table of Contents

Sometimes in our code, we need to output different HTML elements depending on different conditions. For example, you may need to create a navigation menu that can toggle between hidden and shown. To achieve these different scenarios, you need to check the condition and then conditionally output HTML depending on that condition. How do we do this with Svelte? We use what's known as conditional statements, or if/else statements.

Let's start off with a basic example.

<script>
  let product = {
    name: 't-shirt',
    quantity: 0,
  };
  function increment() {
    product.quantity += 1;
  }
</script>
 
<button on:click="{increment}">Increment</button>
<div>This product is in stock</div>
<div>Current {product.name} Quantity: {product.quantity}</div>

Here we have declared a variable product which is an object with the product’s name, ‘t-shirt’, and quantity. We also have a function called increment which adds one to quantity anytime it’s called. We have a button that will call increment any time it’s clicked, as well as a <div> displaying the values of product.name and product.quantity. Lastly, we have a div alerting the user that the product is in stock, but we only want to show this div when the value of quantity exceeds 10. To conditionally render some markup with Svelte, we can wrap it in an if block. In our current example, we would add:

<script>
  let product = {
    name: 't-shirt',
    quantity: 0,
  };
  function increment() {
    product.quantity += 1;
  }
</script>
 
<button on:click="{increment}">Increment</button>
{#if product.quantity > 10}
<h3>This product is in stock</h3>
{/if}
<div>Current {product.name} Quantity: {product.quantity}</div>

Here we have declared a variable product which is an object with the product’s name, ‘t-shirt’, and quantity. We also have a function called increment which adds one to quantity anytime it’s called. We have a button that will call increment any time it’s clicked, as well as a <div> displaying the values of product.name and product.quantity. Lastly, we have a div alerting the user that the product is in stock, but we only want to show this div when the value of quantity exceeds 10. To conditionally render some markup with Svelte, we can wrap it in an if block. In our current example, we would add:

<script>
  let product = {
    name: 't-shirt',
    quantity: 0,
  };
  function increment() {
    product.quantity += 1;
  }
</script>
 
<button on:click="{increment}">Increment</button>
{#if product.quantity > 10}
<h3>This product is in stock</h3>
{/if}
<div>Current {product.name} Quantity: {product.quantity}</div>

Now if you were to test this out in the browser, you will see the div appear once quantity reaches 11. In this example, we don’t see anything until quantity is greater than 10, but what if we want to display a different sentence if quantity is not greater than 10? Since these two conditions are mutually exclusive (i.e. quantity is either greater than 10 or not, it cannot be both), we can do this using an else block before closing our if block. In our code, we would add:

<script>
  let product = {
    name: 't-shirt',
    quantity: 0,
  };
  function increment() {
    product.quantity += 1;
  }
</script>
 
<button on:click="{increment}">Increment</button>
{#if product.quantity > 10}
<div>This product is in stock</div>
{:else}
<h3>Only a few items left in stock!</h3>
{/if}
<div>Current {product.name} Quantity: {product.quantity}</div>

Now, we will see the <div> displaying "Only a few items left in stock!" until quantity reaches 11, at which point the other <div> is displayed. We can take this a step further and display yet another <div> if quantity is 0. With Svelte, multiple conditions can be chained together with else if. In our example we would add:

<script>
  let product = {
    name: 't-shirt',
    quantity: 0,
  };
  function increment() {
    product.quantity += 1;
  }
</script>
 
<button on:click="{increment}">Increment</button>
{#if product.quantity > 10}
<div>This product is in stock</div>
{:else if product.quantity === 0}
<div>This product is out of stock</div>
{:else}
<div>Only a few items left in stock!</div>
{/if}
<div>Current {product.name} Quantity: {product.quantity}</div>

Now we will see three different sentences displayed depending on whether quantity is 0, greater than 0 but less than or equal to 10, or greater than 10.

Looking back at our code, this syntax probably feels unfamiliar, so let's go over it. A # character always indicates a block opening tag. A / character always indicates a block closing tag. A : character, as in :else, indicates a block continuation tag. Thankfully, this is almost all of the unique syntax that Svelte adds to HTML, and the same block syntax is used to loop over lists of data. Let's learn how to do that.

Let’s start off by creating an array called products containing multiple product objects, each with a name and quantity.

let products = [
  { name: 't-shirt', quantity: 10 },
  { name: 'mug', quantity: 30 },
  { name: 'sticker', quantity: 8 },
  { name: 'sweatshirt', quantity: 12 },
];

In order to iterate through this array and display the name of each product, we will once again use our block syntax, this time using an each block. Our code will look like the following:

<script>
  let products = [
    { name: 't-shirt', quantity: 10 },
    { name: 'mug', quantity: 30 },
    { name: 'sticker', quantity: 8 },
    { name: 'sweatshirt', quantity: 12 },
  ];
</script>
 
{#each products as product}
<h3>{product.name}</h3>
{/each}

Here, we have created an each block which will loop through our array. In this example, the expression, products, is our array, but it can be any array or array-like object (i.e. anything that has a length property), and product is the alias for each item in our products array. This alias can be anything we’d like it to be. It could just as easily be {#each products as p} or even {#each products as hello}. We can also get the current index as a second argument, like this:

<script>
  let products = [
    { name: 't-shirt', quantity: 10 },
    { name: 'mug', quantity: 30 },
    { name: 'sticker', quantity: 8 },
    { name: 'sweatshirt', quantity: 12 },
  ];
</script>
 
{#each products as product, i}
<div>{product.name}</div>
<div>Index: {i}</div>
{/each}

When we use each blocks like this, it’s considered good practice specify a unique identifier, or a key, for each element within our array. In our example, let's add parentheses after our index, and within it I will put product.name. Now, each item in our array has its own unique key.

<script>
  let products = [
    { name: 't-shirt', quantity: 10 },
    { name: 'mug', quantity: 30 },
    { name: 'sticker', quantity: 8 },
    { name: 'sweatshirt', quantity: 12 },
  ];
</script>
 
{#each products as product, i (product.name)}
<div>{product.name}</div>
<div>Index: {i}</div>
{/each}

In this case, the unique identifier for each item is the item's name. This works because each item's name is different, but if there were two items with the same name this wold not work. Each item’s key must be unique. Why is it important to key our each blocks? Svelte can use the key to keep track of which DOM element is connected to which item in the array.

By default, any time you modify the value of an each block, it will add and remove items at the end of the block, and update any values that have changed, which often times is not what you want. This is easier to show than to explain. Let’s remove our key and add a checkbox in this each block as well as a button that calls the method addProduct when it is clicked. This method adds a new product to out products array.

<script>
  let products = [
    { name: 't-shirt', quantity: 10 },
    { name: 'mug', quantity: 30 },
    { name: 'sticker', quantity: 8 },
    { name: 'sweatshirt', quantity: 12 },
  ];
  function addProduct() {
    let cup = { name: 'cup', quantity: 4 };
    products = [cup, ...products];
  }
</script>
<button on:click="{addProduct}">Add Product</button>
{#each products as product, i}
<div>{product.name}</div>
<div>Index: {i}</div>
<input type="checkbox" />
{/each}

Now, if you test this in the browser and check this first checkbox alongside the 't-shirt' product and then click the button, you will see the checked checkbox is now aligned to the new product, 'cup', not 't-shirt'. Whats happening is the DOM is adding a new node, and then updating each node to match our each block. Instead, we want to add a new DOM node, and leave the others unaffected. This is done by keying our each block. To avoid this issue, it’s good practice to always specify a unique identifier, or a key, for each element within our array when working with each blocks. Let's add our key back in. Now, each item in our array once again has its own unique key which Svelte can use to keep track of which DOM element is connected to which item in the array.

<script>
  let products = [
    { name: 't-shirt', quantity: 10 },
    { name: 'mug', quantity: 30 },
    { name: 'sticker', quantity: 8 },
    { name: 'sweatshirt', quantity: 12 },
  ];
  function addProduct() {
    let cup = { name: 'cup', quantity: 4 };
    products = [cup, ...products];
  }
</script>
<button on:click="{addProduct}">Add Product</button>
{#each products as product, i (product.name)}
<div>{product.name}</div>
<div>Index: {i}</div>
<input type="checkbox" />
{/each}

Now, each block is linked under the hood with a specific piece of data in the array. If you check one of these check boxes and click the button, the correct checkbox will still be checked. This step can potentially be skipped over if not careful, but keying your each blocks is a really good habit to get into.

That sums up this module, in the next module we will learn how to add some style to our app with CSS!