Growing

How to pass values between components in Svelte 5

d3

svelte

Svelte version 5 was introduced in October 2024 and with it came new syntax for passing values from one component to another. This post covers my explorations as I figure out how to use $props() with $state() to pass values from parent to child, setContext() to pass values from parent to grandchild, and the various way I may be able to pass values between siblings.

Intro

When I first started building data visualizations for the web, most of my code was written in a single, long javascript file. Because each project I built was bespoke and I was the only one working with this code, it didn’t terribly matter how my code was organized. That said, for big projects, it was easy to get lost in my one enormous file.

Eventually, I was introduced to the idea of using separate .js files, specifically in a framework like svelte, to help better organize my code based on what each piece actually does. While I don’t believe that,

As a reminder, I don’t have a formal background in software development, so it is possible that there are ✨very good reasons ✨ for using one system of organization or the other. Also, if you’re coding in a work environment where multiple people need to interact with your code, you should probably try to stick to what the team is doing so that you can all work together better.

But if you’re building something on your own, or just for fun, I say build it however your heart desires 💖

I did find it useful for me, personally, to begin organizing my code using separate files. In doing so, I had to learn new things, and the main difference for me between working with one giant file and separate, better-defined files, was understanding how to pass information between these separate files.

In the world of data viz creation, I often need to pass lots of values around to the various files in my workspace. For example, imagine I’m making a relatively straightforward scatter plot. I may need to create a scale function to convert my raw values to pixel values. I need to make sure that my Dots.svelte file (which is responsible for drawing just the points) and my AxisX.svelte file (which is responsible for drawing the x axis) use the exact same scale. Rather than calculating it the same way in both files, I’d prefer to calculate it once and then make sure to pass the same value to both files.

I initially started learning how this works in svelte version 4, but with the release of version 5 in October 2024, I needed to learn the new ways to pass information between my components. This was written as I familiarized myself with the new syntax to help me understand it better, but hopefully it’ll help some of you as well.

Passing values from parents to children

Likely the most common way you’d want to pass values between components in Svelte is between a parent component and its children. There are a few ways to make that happen:

Passing values as props

Probably the simplest way to pass values between parents to children is to pass it as a prop of the child component. In Svelte 4, this would require

S4_Parent.svelte

<script>
import Child from './Child.svelte'

// define the value we want to pass to the child
const valueToPass = 'something awesome'
</script>

<Child value = {valueToPass} />

S4_Child.svelte

<script>

// define the incoming props
export let value;

</script>

<p>{value}</p>

Now, in Svelte 5, we do something very similar, but define it differently, only in the child. So, the Parent.svelte file stays the same, but in Child.svelte we use the new $props() rune.

S5_Child.svelte

<script>

// define the incoming props
const { value } = $props();

</script>

<p> {value} </p>

Unfamiliar with this syntax?

When you see something like const {...} = thing in javascript, this is referred to as destructuring and it allows you to assign elements in an array or properties within an object to new variable names.

Great, so we can now pass values from parents to children. If your child component needs to also pass this value to its own children (i.e., the grandchild of our Parent.svelte component), you can pass this value again:

S5_Child.svelte

<script>
import Grandchild from './Grandchild.svelte'

const {value} = $props();
</script>

<Grandchild passedValue = {value} />

And this works! In the above example, though, the Child.svelte component isn’t actually using this variable. It’s getting it from Parent.svelte just to pass it on to Grandchild.svelte. This is called “prop drilling.” It technically works, but there’s a more efficient way to pass values directly from parents to grandchildren, skipping over the children entirely.

Passing values from parents to grandchildren

In order to pass a value from parent to grandchild without needing to pass it through the child component, we can use Svelte’s context API.

Here, we need to setContext in the Parent.svelte and getContext in the Grandchild.svelte.

S5_Parent.svelte

<script>

import { setContext } from 'svelte'; 

setContext('valueToPass', 'value');

</script>

S5_Grandchild.svelte

<script>

import { getContext } from 'svelte'; 

// access the context set in the Parent.svelte file
const value = getContext('valueToPass'); 

</script>

With the context API, you can set context in any parent element and access it from any downstream components. That is, any children or grandchildren (or great-grandchildren etc.) of the parent. It can’t pass values to any siblings of Parent.svelte (more on this later.)

Another catch for the context API is that it isn’t reactive by default. That is, if something causes valueToPass to change in Parent.svelte, the Grandchild.svelte component won’t know about that unless we change this code slightly.

We’ll want to take advantage of the $state() rune to make our value reactive.

S5_Parent.svelte

<script>

import { setContext } from 'svelte'; 

let value = $state({ text: 'something awesome' })
setContext('valueToPass', value);

</script>

Notice that value is now an object?

This is because the $state() rune only allows us to modify values but not to re-assign them.

You may have expected changing value to a reactive variable to looked like this: let value = $state('something awesome') but if we do that, then value is still not reactive. In order for value to change when the string assigned to it changes, we need to use the $state() rune to monitor for changes. But the $state() rune doesn’t allow you to re-assign a value, only modify it.

So, in many examples of Svelte 5 usage where the creator is building a simple counter, you may have seen syntax like let count = $state(0) and this does work because the counter functions are used to modify the value from 0 either by adding or subtracting from that. But, when you’re working with strings, changing our string from something awesome to another awesome thing is a re-assignment not a modification and $state() can’t handle that. So, the way around it is to make whatever value you want to re-assign a value within an object. So, instead of let value = $state('something awesome') we’ll do let value = $state({text: 'something awesome'}) and we can re-assign value.text as much as we want.

Passing values between siblings

This seems to be the most complicated iteration of passing values between components that I’ve come across, but when making data visualizations, it’s also one of the most common things I attempt to do. Let me give you an example.

Imagine, again, that I’m building a relatively straightforward scatter plot in svelte. I may have a handful of components like this:

  • Dots.svelte - responsible for drawing the points
  • AxisX.svelte - responsible for drawing the x axis
  • AxisY.svelte - responsible for drawing the y axis
  • Tooltip.svelte - responsible for creating a tooltip when the chart is interacted with
  • ScatterPlot.svelte - responsible for bringing all of the pieces of my chart together

Each of these components is largely responsible for creating a single part of my chart, except for ScatterPlot.svelte which may be structured something like this:

S5_ScatterPlot.svelte

<script>

import Dots from './Dots.svelte'
import AxisX from './AxisX.svelte'
import AxisY from './AxisY.svelte'
import Tooltip from './Tooltip.svelte'

</script>

<svg>
	<AxisX />
	<AxisY />
	<Dots />
	<Tooltip />
</svg>

Now, imagine that in my Dots.svelte file, I introduce some logic to record when a user hovers over a data point. I want the creation of that information to trigger displaying the tooltip drawn by Tooltip.svelte. So, Tooltip.svelte needs to know what is being hovered and where the tooltip should be placed to point to that element.

However, Dots.svelte and Tooltip.svelte are siblings.

Technically speaking, in this example, I could make Tooltip.svelte a child of Dots.svelte and then we could use $props() the same way we would with any parent to child relationship.

But, I often draw my charts with svg and render my tooltips or other text-related elements with standard html elements (e.g., <p> or <span>) for easier styling and (sometimes) simpler screen-reader support. That means that often my ScatterPlot.svelte actually looks like this:

S5_ScatterPlot.svelte

<script>

import Dots from './Dots.svelte'
import AxisX from './AxisX.svelte'
import AxisY from './AxisY.svelte'
import Tooltip from './Tooltip.svelte'

</script>

<svg>
	<AxisX />
	<AxisY />
	<Dots />
</svg>

<Tooltip />

Tooltip.svelte being an html element inside the svg wouldn’t work so I can no longer make Tooltip.svelte a child of Dots.svelte. These elements are siblings and we need to be able to pass information between them.

I can technically pass the shared value up from Dots.svelte to ScatterPlot.svelte and then back down to Tooltip.svelte, but that seems like a recipe for disaster, so let’s explore other ways.

In Svelte 4, I would have used stores like this:

S4_stores.svelte

<script>

import { writable } from 'svelte/store';

export const tooltipContent = writable('')

</script>

And then I could set or get that information in Dots.svelte and Tooltip.svelte like this:

S4_Dots.svelte

<script>

import {tooltipContent} from './stores.svelte';

</script>

<circle
	on:hover={(el) => $tooltipContent = el}
></circle>

Now, according to the svelte docs, this method still works:

Stores are still a good solution when you have complex asynchronous data streams or it’s important to have more manual control over updating values or listening to changes.

But apart from the instances mentioned above, the recommendation in Svelte 5 is to use the $state() rune instead. In all of the examples I’ve seen of this, the setup is relatively similar. You still have a separate file outside of Dots.svelte and Tooltip.svelte for your shared value, so that it can be easily accessed by both components. And you can get or set that value from any other component, even siblings. In my example, that would look like this:

S5_stores.svelte.js

// no need to import writable, we'll just use the $state prop
export const tooltipContent = $state({ content: ''})

S5_Dots.svelte

<script>

import {tooltipContent} from './stores.svelte.js';

</script>

<circle
	on:hover={(el) => tooltipContent.content = el}
></circle>

The biggest differences in using $state instead of stores in the svelte 5 implementation are:

  • The shared value is stored in a .svelte.js file instead of a .svelte file
  • No need to import writable or any other specific syntax
  • Unless the shared value is a number that you are simply modifying (e.g., adding, subtracting etc.) the shared value needs to be stored as an object.
  • Similarly, the value needs to be set and accessed by its object key (i.e., tooltipContent.content not just $tooltipContent)
  • We no longer need the $ designation to denote stored values

More to come

There are other ways to pass values between components within svelte. Theoretically, I’ll continue to expand on this post as I continue to learn. But for now, these are my most commonly-used methods of passing values between components in Svelte 5.