Growing
How to pass values between components in Svelte 5
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 pointsAxisX.svelte
- responsible for drawing the x axisAxisY.svelte
- responsible for drawing the y axisTooltip.svelte
- responsible for creating a tooltip when the chart is interacted withScatterPlot.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