Intro
I recently made the switch from React over to Svelte (something I might write about in the future but that’ll need to be for another day).
While working on a personal project I am learning about Svelte & SvelteKit as I go while mostly rely on my existing React knowledge and then trying to understand the Svelte approach when things don’t work as I expect them to.
I wanted on a reusable List
component. The idea of this component is that it takes a list of items as a prop and then loops over them creating a HTML structure of a ul
with content contained within li
s.
The benefit of this component is that you can put anything you want within the li
but the styling of the list has already been defined.
It’s pretty straightforward but making this component in Svelte was a little more involved than I was expecting it to be and required the use of some Svelte that I had not seen before so I thought I’d document what I needed to do.
React
First let’s look at how I would typically build this kind of component in React. Our List
component is responsible for looping over a list of items it receives via props. We do this using the map
method and returning a new list with an li
and the item inside:
import { type ReactNode } from 'react'
type Props = {
items: ReactNode[]
}
export const List: React.FC<Props> = ({ items }) => {
return (
<ul style=>
{items.map((item) => (
<li style=>{item}</li>
))}
</ul>
)
}
The basic component we want to put within our li
for this example is a Card
component. It simply has some padding and border and takes some copy via props:
type Props = {
copy: string
}
export const Card: React.FC<Props> = ({ copy }) => {
return (
<div style=>{copy}</div>
)
}
To then use our List
component we just need to add a list of React components, in this case Card
. The cards and their copy will then be rendered within our ul
and li
s:
root.render(
<React.StrictMode>
<List items={[<Card copy="a" />, <Card copy="b" />, <Card copy="c" />]} />
</React.StrictMode>
)
This will then show a list of cards with a
, b
& c
in the browser. You can view the example in Codesandbox.
Svelte
So how do we do the same in Svelte? Well it’s a little different than it is in React. We need to make use of a special Svelte element called svelte:component.
This special element allows for dynamic component rendering. Using the this
prop we can tell the svelte:component
what component it needs to be. We can then pass any props we want to it.
Since we can’t know what props will be needed for all components ahead of time we need to spread all the props and leave the component to figure it:
<script>
export let items;
</script>
<style>
ul {
margin: 0;
padding: 0;
list-style: none;
}
li + li {
margin-top: 1rem;
}
</style>
<ul>
{#each items as item}
<li>
<svelte:component this={item.component} {...item} />
</li>
{/each}
</ul>
Our Card
component in Svelte is very similar to the React version. It expects a copy
prop to be passed so that it can render it within a div
and applies some basic styling:
<script>
export let copy;
</script>
<style>
div {
padding: 1rem;
border: 1px solid grey;
}
</style>
<div>
{copy}
</div>
Usage of the List
component then requires us to pass a list of objects. One that states which component to use; in our case, Card
and then any of the expected props that it needs.
It’s a little more verbose compared to the React version but in principle, it’s the same:
<script>
import Card from './Card.svelte'
import List from './List.svelte'
let items = [
{
component: Card,
copy: 'a'
},
{
component: Card,
copy: 'b'
},
{
component: Card,
copy: 'c'
}
]
</script>
<main>
<List {items} />
</main>
This will then show a list of cards with a
, b
& c
in the browser. You can view the example in Codesandbox.
Hopefully this is helpful for others since it took a bit of googling to find out how to do this kind of component in Svelte.