Linguine Code

How to pass a Svelte component to another Svelte component

How in the world do you pass a Svelte component down to another Svelte component?

Well, you can use the <slot> directive in Svelte.


<!-- Child component -->
<div>
  <slot />
</div>

<!-- Parent Component -->
<childComponent>
  <h1>Booya!</h1>
</childComponent>

But that method has its limitations. The <slot> directive cannot be dynamic.

For example, what if you have an array of Svelte components?


import TabContent1 from './TabContent1';
import TabContent2 from './TabContent2';
import TabContent3 from './TabContent3';

const tabItems = [
  { label: "Tab 1",
    value: 1,
    component: TabContent1
  },
  { label: "Tab 2",
    value: 2,
    component: TabContent2
  },
  { label: "Tab 3",
    value: 3,
    component: TabContent3
    }
];

Since the <slot> directive cannot be dynamic, how do you handle this type of scenario?

The solution

To pass Svelte components dynamically down to a child component you have to use Svelte’s <svelte:component> directive.


<svelte:component this={some_svelte_component}>

<svelte:component> accepts a property called this.

If the property this has a component attach to it than it will render the given Svelte component.

But if the value is falsy (false, undefined, null) than it will not render.

Here’s a quick overview of what that would look like:


<!-- Parent component -->
<script>
  import ChildComponent from './ChildComponent.svelte';
  import Content1 from './Content1';

  const items = [
    { component: Content1 }, // Will render
    { component: null }, // Will not render because value is null
    { }, // Will not render because `component` is undefined
  ];
</script>

<ChildComponent items={items} />

<!-- Child component: ChildComponent.svelte -->
<script>
  export let items = [];
</script>

{#each items as item}
  <svelte:component this={item.component} />
{/each}

In a recent article, I created a very simple Svelte Tab component. You can find the article here, “Building a tabs component with Svelte“.

In this article code example, I’m going to revamp the Tabs component that I’ve made previously by using the <svelte:component> directive.

Let’s get started!

Step 1: Update the Svelte Tabs component

The first step is to update the Tabs Svelte component.


<script>
  export let items = [];
  export let activeTabValue = 1;

  const handleClick = tabValue => () => (activeTabValue = tabValue);
</script>

<ul>
  {#each items as item}
    <li class={activeTabValue === item.value ? 'active' : ''}>
      <span on:click={handleClick(item.value)}>{item.label}</span>
    </li>
  {/each}
</ul>

<div>
  {#each items as item}
    {#if activeTabValue === item.value}
      <svelte:component this={item.component} />
    {/if}
  {/each}
</div>

<style>
  ul {
    display: flex;
    flex-wrap: wrap;
    padding-left: 0;
    margin-bottom: 0;
    list-style: none;
    border-bottom: 1px solid #dee2e6;
  }

  span {
    border: 1px solid transparent;
    border-top-left-radius: 0.25rem;
    border-top-right-radius: 0.25rem;
    display: block;
    padding: 0.5rem 1rem;
    cursor: pointer;
    margin-bottom: -1px;
  }

  span:hover {
    border-color: #e9ecef #e9ecef #dee2e6;
  }

  li.active > span {
    color: #495057;
    background-color: #fff;
    border-color: #dee2e6 #dee2e6 #fff;
  }

  div {
    padding: 24px 16px;
  }
</style>

The first step I took is to reduce some of the boilerplate code from the previous example.

I’ve removed the onMount Svelte hook, and gave activeTabValue a default value of 1.

The next big update to this file is to render the components that are found in the array of items.

Step 2: Create the Tab item components

Now I’m just creating 3 different Svelte components that would be for each tab.

In the, “Building a tabs component with Svelte“, The App.svelte contained the content for each tab.

So now, I’m extracting them out.


<h2>Here's tab content 1</h2>

<h2>Here's tab content 2</h2>

<h2>Here's tab content 3</h2>

Step 3: Update the App Svelte component

Just liked mentioned above, the App.svelte file was containing the content for the tabs.

But now that I’ve extracted that out, I’m importing those Svelte components into the App.svelte file.


<script>
  import Tabs from "./components/Tabs.svelte";

  import TabContent1 from "./components/TabContent1";
  import TabContent2 from "./components/TabContent2";
  import TabContent3 from "./components/TabContent3";

  // List of tab items with labels and values.
  let items = [
    {
      label: "Tab 1",
      value: 1,
      component: TabContent1
    },
    {
      label: "Tab 2",
      value: 2,
      component: TabContent2
    },
    {
      label: "Tab 3",
      value: 3,
      component: TabContent3
    }
  ];
</script>

<Tabs {items} />

In my array, items, I’ve added a new property called component.

So each tab item will be mapped to it’s own tab content.

All that’s left is to pass the variable items down to the Tabs component.

Conclusion

<svelte:component /> makes it possible to drop components in anywhere without putting too much markup in the component where it’s being rendered.