Adam Johnston

Mapping components in Svelte vs React

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 lis.

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 lis:

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.