Working With Components

In this module will learn how to create Svelte components and how they work.
Table of Contents
Working With ComponentsView the code for this module.
Course Version History
  • Nov. 21, 2022 - Updated to SvelteKit v1.0.0-next.549. Changed index.svelte to +page.svelte.

In this module will learn how to work with Svelte components. Up to this point we have mostly been working out of one component, our +page.svelte component. Typically, a Svelte project would be composed of many different components. For example, a basic website might have a header, footer, dropdown menu, etc. We would then combine these components in a tree-like structure to compose our application.

If we take a look at this diagram, we see that our parent component is the home page. Within our page components we can have child components such as a header, footer, and article component. These child components can have their own child components and so on. Our application is built out of all these different components and pages that form a tree like structure.

Now, it’s important to make a clear distinction between pages and components. All pages are components, but not all components are pages. For instance, we have been working out of this +page.svelte component, which is a page component, meaning it corresponds to a specific url that we can visit in our browser. Components that define a page or a layout, such as our index page, can import and use child components, but they themselves cannot be imported as a child.

For instance, our +page.svelte page can import components such as our GridTile.svelte component, but our GridTile.svelte file can not import our +page.svelte page. If we attempt to do so, we will get an error. Now, one more time, just to really drill this into your head, pages can never be imported by another page or component.

Let’s learn how to create Svelte components. Any file that ends with .svelte is a Svelte component. If this file lives in the /src/routes directory, it will be a page, meaning it has a coresponding route that we can navigate to in our browser. If the file lives in our /src/lib directory, it is just a basic svelte component that can be imported and used by other components. Now, let’s look at the structure of a .svelte file in the code snippet below. At this point, we've already been working out of a Svelte component, so it should look pretty familiar. A .svelte file is a superset of HTML, so just like an HTML file, it has a <script> tag, where we put all of our logic, the file can also contain HTML, as well as a <style> tag for CSS. We covered CSS two modules ago and how to install tailwind in the last module, so we will just be using tailwind from here on out.

Now if you are not too familiar with component-driven development, components are reusable blocks of code that encapsulate HTML, CSS, and JavaScript in a single file. For example, a dropdown menu is an example of a component. This allows us to easily reuse the same chunk of code as many times as we want without re-writing it. It also keeps our JavaScript and CSS scoped to that specific component. In our example, we see our GridTile component displayed in our browser, and everything needed to build this component is bundled in a single re-useable file. To use a component we import it in the parent’s script tag, and use it in our HTMl like this.

+page.svelte
<script>
  import GridTile from '$lib/GridTile.svelte';
</script>
 
<GridTile />

Now, since components are re-useable, we can add this as many times as we want. Instead of copying and pasting all the code needed to make this component, you only need to add one more tag like this:

+page.svelte
<script>
  import GridTile from '$lib/GridTile.svelte';
</script>
 
<GridTile />
<GridTile />
<GridTile />

Now, let’s declare a variable products which is a list of product objects each containing a name, src, and price. We can use an each block to iterate through this list and display a grid tile for each item.

+page.svelte
<script>
  import GridTile from '$lib/GridTile.svelte';
  let products = [
    {
      title: 'Mug',
      cost: '$10',
      src: '/path.png',
    },
    {
      title: 'Shirt',
      cost: '$10',
      src: '/path.png',
    },
  ];
</script>
 
<main>
  {#each products as product (product.title)}
  <GridTile />
  {/each}
</main>

The problem here is that all of the info being displayed in our component is hard coded, so we will see the same image, name and cost for each item. Just like CSS, logic is also scoped to the component. If we move into our GridTile component and try to log products, we will see it throws an error that products is not defined. How can we use the value of each product in our GridTile component? We can do this using properties.

Props give us a way to set data in a child component from the parent. To do this, we first need to export product from our GridTile component like this.

GirdTile.svelte
<script>
  export let product;
  console.log(product);
</script>
 
<div class="h-[50vh] w-full overflow-hidden bg-indigo-600">
  <div class="relative flex h-full w-full items-center justify-center">
    <img
      src="https://cdn.shopify.com/s/files/1/0434/0285/4564/products/Cup-front-black.png?v=1623159405"
      class="w-full flex-none transition duration-300 ease-in-out md:w-1/2 lg:w-full"
      alt=""
    />
    <div class="absolute left-0 top-0 text-white">
      <div class="bg-black p-3 text-2xl  font-medium">Cup</div>
      <div class="w-fit bg-black p-3 text-sm">$20.00</div>
    </div>
  </div>
</div>

This will expose the variable to the parent from the child, where we can then assign its value. In our parent component we can add product as a param to our GridTile component and assign its value to product like this:

+page.svelte
<script>
  import GridTile from '$lib/GridTile.svelte';
 
  let products = [
    {
      title: 'Mug',
      cost: '$10',
      src: '/path.png',
    },
    {
      title: 'Shirt',
      cost: '$10',
      src: '/path.png',
    },
  ];
</script>
 
<main>
  {#each products as product (product.title)}
  <GridTile product="{product}" />
  {/each}
</main>

Now that we’re passing in the value of each product into our grid tile component as a parameter, we will no longer get an error and the value of each product is logged in our component. We can now update the title, src, and cost to use the values passed in with our prop. Now if you were to run this in the browser, you would see the values of each product in our array displayed on the page. Anytime product changes in the parent, it will also update in our child component. To prove this, let's add a button to the parent component that will call an updateCost function which will change the cost of the first item in our array.

+page.svelte
<script>
  import GridTile from '$lib/GridTile.svelte';
 
  let products = [
    {
      title: 'Cup',
      cost: '$10',
      src: 'https://cdn.shopify.com/s/files/1/0434/0285/4564/products/Cup-front-black.png?v=1623159405',
    },
    {
      title: 'Shirt',
      cost: '$10',
      src: 'https://cdn.shopify.com/s/files/1/0434/0285/4564/products/short-sleeve-t-shirt-0.png?v=1622902418',
    },
  ];
 
  function updateCost() {
    products[0].cost = '$50';
  }
</script>
 
<button on:click="{updateCost}" class="my-4 bg-pink-500 p-2 text-white">
  Change Price
</button>
{#each products as product}
<GridTile product="{product}" />
{/each}

Now, if we click this button, when the value of the price changes in the parent, the child component is updated as well. But what would happen if we didn’t pass a value in for our prop? Let’s test this out. I’ll add another GridTile tag, this time without defining our prop product. Now we will be thrown an error. We can fix this by giving our prop a default value. This way, if the parent component does not set the value, it will default to whatever we set in the child component. Let's move back into our GridTile component and give our property product a default value like this:

GridTile.svelte
<script>
  export let product = {
    title: 'No product provided',
    src: 'https://img.icons8.com/ios/500/no-image.png',
    cost: '$0',
  };
</script>
 
<div class="h-[50vh] w-full overflow-hidden bg-indigo-600">
  <div class="relative flex h-full w-full items-center justify-center">
    <img
      src="{product.src}"
      class="w-full flex-none transition duration-300 ease-in-out md:w-1/2 lg:w-full"
      alt=""
    />
    <div class="absolute left-0 top-0 text-white">
      <div class="bg-black p-3 text-2xl  font-medium">{product.title}</div>
      <div class="w-fit bg-black p-3 text-sm">{product.cost}</div>
    </div>
  </div>
</div>

Now, if the GridTile component is ever used without setting the value of product, the default values will be displayed.

One last thing. Here, the property name, product, and the value it is being set to in the parent, also product, have the same name. Because of this, we can use shorthand. Instead of saying product={product}, we can say {product}.

+page.svelte
<main>
  {#each products as product}
  <GridTile {product} />
  {/each}
</main>

Now we know how to pass data from the parent component into the child, but what if we want to pass from child to parent? We will go over how to do this in the next module.