3 different ways of cross-component communication in Svelte

· 4 min

How do you pass data between Svelte components? How do you communicate between children and parents? For example, when you need to trigger some action or change state in the parent from a child component. A typical case is to toggle Svelte component's visibility from itself when the visibility state lives in the parent.

Problem

In your parent component you have three different child components that you want to toggle visibility for. In every child component there is a "close" button. When it's clicked the component should hide itself. Since the "close" button is within each child component, you as a parent can't really control it. What are your options?

Turns out it's quite easy to do in Svelte. I can think of three different ways. Let's go through them all.

Setting up the project

Let's dive straight in. The best way to learn is to actually write the code. That way your brain will have time to process the information because it's faster then your typing. We will start by creating a new project.

$ npx degit codechips/svelte-starter-template cross-component-communication
$ cd cross-component-communication && yarn install
$ yarn start

Done? Great! We should have a local server running. Now, create tree simple Svelte components in src/components folder. Create the folder first.

<!-- One.svelte -->
<div class="component">
<h2>Component One</h2>
<img src="https://media.giphy.com/media/1Ju5mGZlWAqek/giphy.gif" alt="happy" />
<button>close</button>
</div>

<!-- Two.svelte -->
<div class="component">
<h2>Component Two</h2>
<img src="https://media.giphy.com/media/lcySndwSDLxC4eOU86/giphy.gif" alt="confused" />
<button>close</button>
</div>

<!-- Three.svelte -->
<div class="component">
<h2>Component Three</h2>
<img src="https://media.giphy.com/media/EdRgVzb2X3iJW/giphy.gif" alt="amazed" />
<button>close</button>
</div>

Nothing fancy yet. We will extend them later. Now change App.svelte to the code below.

<!-- App.svelte -->

<style>
:global(button) {
display: block;
margin-top: 1rem;
}
:global(body) {
font-family: sans-serif;
font-size: 1.25em;
background-color: #262626;
color: #cbd5e0;
}
:global(h1){
margin-bottom: 1em;
margin-block-end: 0;
}
:global(.component) {
padding: 1rem 2rem;
background-color: #67ecd4;
margin: 0 2rem 0 0;
display: inline-block;
min-height: 28rem;
color: #262626;
}
.menu {
display: flex;
margin-bottom: 1rem;
}
.menu button {
margin-right: 1rem;
}
.container {
display: flex;
}
</style>

<script>
// import slide animation for some visual FX
import { slide } from 'svelte/transition';

import One from './components/One.svelte';
import Two from './components/Two.svelte';
import Three from './components/Three.svelte';

// define booleans for our component visibility status
let showOne = false;
let showTwo = false;
let showThree = false;

</script>

<h1>Svelte Component Communication Examples</h1>

<div class="menu">
<!-- show component actions -->
<button on:click={() => (showOne = !showOne)}>show one</button>
<button on:click={() => (showTwo = !showTwo)}>show two</button>
<button on:click={() => (showThree = !showThree)}>show three</button>
</div>

<div class="container">
{#if showOne}
<div transition:slide|local>
<One />
</div>
{/if}

{#if showTwo}
<div transition:slide|local>
<Two />
</div>
{/if}

{#if showThree}
<div transition:slide|local>
<Three />
</div>
{/if}
</div>

Nothing fancy yet here either. All our components are hidden by default and you can make then visible by clicking on the show buttons. But how can we hide the children if the visibility state is defined in the parent? All of our children has close buttons, but they have no logic bound to them. Let's fill that part in.

Option One - Passing Down the Handler

This is the most straight forward option. You encapsulate parent's state in a closure and pass it down to the child component that triggers in when needed. In this case we bind it to the button's on:click event. Change One.svelte component to the following code.

<script>
// a noop function that will be overridden
// by passed-in handler
export let closeHandler = () => {};
</script>

<div class="component">
<h2>Component One</h2>
<img src="https://media.giphy.com/media/1Ju5mGZlWAqek/giphy.gif" alt="happy" />
<button on:click={closeHandler}>close</button>
</div>

What's going on here? We are exposing a closeHandler function from the component. This means that we can pass a property closeHandler to the component from our parent. We initially assign it to a dummy noop function so it doesn't blow up when we click the button and no handler was passed. Let's add this.

// add this function to the App.svelte
const closeOne = () => (showOne = false);

We also need to pass the closure to our One.component. Change to this in App.svelte.

<!-- we pass in our local handler to the component -->
<One closeHandler={closeOne} />

Now it should be possible to close the component from the component itself. First option completed!

Option Two - Binding State to Local Variable

If you export a variable in the child component you can bind it in the parent by using bind:var={local} property where var is the variable name in the child component and local is the parent's local variable.

<!-- Two.svelte -->

<script>
// since the variable is exported we can bind
// to it in our App.svelte
export let show = false;
</script>

<div class="component">
<h2>Component Two</h2>
<img src="https://media.giphy.com/media/lcySndwSDLxC4eOU86/giphy.gif" alt="confused" />
<button on:click={() => (show = false)}>close</button>
</div>

The only thing we need to do in App.svelte is to bind it to local variable.

<!-- we bind Two.show variable to our local showTwo variable -->
<Two bind:show={showTwo} />

Tip: if the variable name is the same both in parent and child then you can use a shortcut bind:foo where foo is the shared variable name.

It should now be possible to close the component. Second option is now complete!

Option Three - Dispatching Messages

The last option you have is to use Svelte's dispatcher to send messages between components. This is the most flexible option, but also the most complex one of the three. Personally, I don't use it that much as I don't like that you have to destructure the received event. It feels noisy for some reason.

Change the code in our Three.svelte component to following.

<!-- Three.svelte -->

<script>
import { createEventDispatcher } from 'svelte';

const dispatch = createEventDispatcher();

// when the handler is triggered in on:click
// can subscribe to the on:close message in our App.svelte
// and do what's needed
const closeHandler = () => dispatch('close');
</script>

<div class="component">
<h2>Component Three</h2>
<img src="https://media.giphy.com/media/EdRgVzb2X3iJW/giphy.gif" alt="amazed" />
<button on:click={closeHandler}>close</button>
</div>

We also need to subscribe to the message in App.svelte. Change to following.

<!-- we listen for close message triggered in the component -->
<!-- we don't care about data in this case as it's not needed -->
<Three on:close={() => (showThree = false)} />

What's going on here? We initialize an event dispatcher in our component and then in the on:click handler we dispatch a close message that parent listens to. Since we only care about the message name we don't need to send any data.

Note: dispatcher can only be used in parent/child relationships. Meaning, you cannot use dispatcher to send messages from parent to children. Only the other way around.

You should now be able to close the third component as well. Third option is also done.

Conclusion

There you go. By now you should know three different ways of how to share data between components. Which one is the best depends on your use case of course. Start simple and adjust to your needs if it doesn't work. If you have more complex needs, where multiple components need to "listen" for data, then Svelte's stores might be a better option.

You can find the example code here https://github.com/codechips/svelte-component-communication