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