3

I would like to conditionally render components with a switch case statement in Svelte like so:

// index.svelte

<script>
import TextContent from './components/text-content.svelte'
import { someData } from './api/some-data.js'


    const ContentSwitch = (data) => {
        switch (data._type) {
            case 'block':
                return data.children.map((child) => ContentSwitch(child));
            case 'span':
                return (
                    <TextContent>
                        <span slot="text-content">{data.text}</span>
                    </TextContent>
                );
        }
        for (let index = 0; index < data.length; index++) {
            return data.map((item) => ContentSwitch(item));
        }
    };

</script>

<div>
    {#each someData as data}
        {ContentSwitch(data)}
    {/each}
</div>

TextContent component:

// components/text-content.svelte

<slot name="text-content">
    <span />
</slot>

It seems that this approach does not work in Svelte as I'm getting an Unexpected Token error.

Is rendering components with a switch possible in Svelte?

Tim Peterson
  • 51
  • 1
  • 6

2 Answers2

5

What you are writing there resembles more JSX which is for React. In Svelte you do not write HTML in your JavaScript but instead keep those separate.

What you would do is make a lookup table and use svelte:component to render the correct component:

<script>
  const BlockTypes = {
    "span": TextContent
  }
</script>

{#each children as child}
  {#if child.type === 'block'}
    <svelte:self {...child} />
  {:else}
    <svelte:component this={BlockTypes[child.type]} {...child} />
  {/if}
{/each}

The svelte:self is under the assumptions that this is in itself also an element of type block, for reasons you cannot import a component into itself so you need this special case here. Having this gives you nested blocks out of the box.

In this example you pass all the properties of the child on to the rendered component so you would have to rewrite your components slightly, you could also use slots but that would be a severe mess with named slots.

Stephane Vanraes
  • 14,343
  • 2
  • 23
  • 41
  • Using svelte:self is a short and elegant way to solve this, that's right! The example in the tutorial might help to understand the concept [svelte-self](https://svelte.dev/tutorial/svelte-self) – Corrl Oct 26 '21 at 10:06
0

I think returning the html syntax in the switch 'span' inside the (java)script tag can't work like this.
Actually it's not a switch between different components but rendering differently nested 'data.text' fields all inside a TextContent component?

What's the structure of someData? Assuming it looks something like this

let someData = [
{
  _type: 'block',
  children: [{
  _type: 'span',
  text: 'textValue#1'
  }]
},
{
  _type: 'span',
  text: 'textValue#2'
}
]

A recursive function could be used to get all nested text fields

    function getSpanTexts(dataArr) {
        return dataArr.flatMap(data => {
            switch (data._type) {
            case 'block':
                return getSpanTexts(data.children)
            case 'span':
                return data.text                
        }
        })
    }
    
    $: textArr = getSpanTexts(someData)  // ['textValue#1', 'textValue#2']

The text fields then can be iterated with an each loop inside the html, each rendered inside a TextContent component

<div>
    {#each textArr as text}
         <TextContent>
             <span slot="text-content">{text}</span>
         </TextContent>
    {/each}
</div>

See this working REPL of the code snippets

Corrl
  • 6,206
  • 1
  • 10
  • 36