Skip to content

[Snippets]: Svelte 5 Snippets don't support programmatic manipulation #10439

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
fallaciousreasoning opened this issue Feb 9, 2024 · 10 comments
Closed
Labels
awaiting submitter needs a reproduction, or clarification

Comments

@fallaciousreasoning
Copy link
Contributor

fallaciousreasoning commented Feb 9, 2024

Describe the problem

I'm really liking the way Svelte 5 is going - the $$props rune in particular is going to be a massive improvement. However, I'm quite concerned about snippets.

As I understand it, they've been created because Svelte doesn't have an easy way to pass around bits of DOM. In React (or SolidJS) this is pretty easy, because you can simply assign a component (or fragment of JSX) to a variable.

For example:

const foo = <div>Hello, world!</div>

// or for a dynamic greeting:
const greet = (name: string) => <div>Hello, {name}</div>

Snippets do solve this problem in Svelte, but it does it by introducing a new, second class sort of component, Snippets. As they currently stand, they're very similar to normal Svelte components, but with a few important differences:

  • They can be created from inside a Svelte component
  • They don't have their own style or script tag, just the Svelte markup

This results in a bit of weirdness about how I use Snippets, vs how I use Components:

// the component
<Counter start={5} />

// versus the snippet
{@render counter(5)}

and I can't easily put a <Counter/> into a variable without wrapping it in a snippet.

Describe the proposed solution

My proposed solution would be to introduce $svelte rune that allows you to define a Svelte component.

// in MyComponent.svelte

<script>
  const names = []
  const greeterTemplate = $svelte(item => <div>Hi, {name}</div>)

  // no props - dunno if this is useful or not?
  const staticGreeting = $svelte(<div>Hi {names.join(', ')}</div>)
</script>
{#each names as name}
  {@render greeterTemplate(name)}
{/each}

@render staticGreeting

Rewriting the Snippets example to use it would look something like this:

// Table.tsx
<script>
  let { data, header, row } = $props();
</script>

<table>
  {#if header}
  <thead>
    <tr>{@render header()}</tr>
  </thead>
  {/if}

  <tbody>
    {#each data as d}
    <tr>{@render row(d)}</tr>
    {/each}
  </tbody>
</table>

<style>
  table {
    text-align: left;
    border-spacing: 0;
  }

  tbody tr:nth-child(2n+1) {
    background: #eee;
  }

  table :global(th),
  table :global(td) {
    padding: 0.5em;
  }
</style>

// App.tsx
<script>
  import Table from './Table.svelte';

  const fruits = [
    { name: 'apples', qty: 5, price: 2 },
    { name: 'bananas', qty: 10, price: 1 },
    { name: 'cherries', qty: 20, price: 0.5 }
  ];

  const header = $svelte(<>
    <th>fruit</th>
    <th>qty</th>
    <th>price</th>
    <th>total</th>
  </>)
  
  const row = $svelte(d => <>
    <td>{d.name}</td>
    <td>{d.qty}</td>
    <td>{d.price}</td>
    <td>{d.qty * d.price}</td>
  </>)
</script>

<Table data={fruits} {header} {row}/>

Note: I think this would fix #9980 too.

As a bonus, it would be good (but not necessary) to be able to render functional $svelte runes the same way we do components:

<script>
    const Child = $svelte(props => <div>Foo</div>)
</script>
<Child/>

Importance

would make my life easier, would make Svelte more consistent

@dummdidumm
Copy link
Member

I don't really understand yet what use cases would be possible with your proposal that aren't already possible with components and snippets in its current form. Is this purely about authoring style, i.e. you prefer to write your snippets inside the script tag?
FYI you have access to a snippet in the scope its created in. So this is valid code:

<script>
   foo; // can access the snippet here, and for example pass it into context etc
</script>

{#snippet foo()}
  ..
{/snippet}

@dummdidumm dummdidumm added the awaiting submitter needs a reproduction, or clarification label Feb 9, 2024
@q1b
Copy link

q1b commented Feb 9, 2024

Can we export the snippets outside of svelte file, and import it in another svelte file!
Or for now only prop drilling is possible

@brunnerh
Copy link
Member

Snippets can also be passed around via context or events.
They are more geared toward local markup reuse and replacing of the old slot syntax.

If you want to pass markup definitions between otherwise unrelated files, I would recommend just using a regular component.

@fallaciousreasoning
Copy link
Contributor Author

fallaciousreasoning commented Feb 11, 2024

FYI you have access to a snippet in the scope its created in. So this is valid code:

Ah okay - I didn't realise that. In that case, I guess it is solely a style thing, and in that case, not too important.

FWIW, it does feel slightly magic/backward to be able to access the snippet in markup without declaring it, but I guess if you squint at the snippet as a function, it kinda makes sense.

The other half of my concern, is that it means there are two different ways of doing the same thing (creating a reusable bit of parametized markup) and there are differences to what you can do with each (or at least, how easy it is).

@dummdidumm
Copy link
Member

While yes, there are now two ways for some cases, it should be intuitively clear most of the time which to use; and the final docs will go into more detail on this.
Closing since the original feature request is addressable through snippets being available in the scope they're declared in.

@dummdidumm dummdidumm closed this as not planned Won't fix, can't repro, duplicate, stale Feb 12, 2024
@andersekdahl
Copy link

One other use case that keeps coming up for me is how to deal with a component that wants a variable number of snippets passed to it? There might be something I'm missing but I would like to be able to do something like this:

<Tabs
  items={ [
    {
      text: 'Tab 1',
      content: $svelte(<p>A snippet here</p>)
    },
    {
      text: 'Tab 2',
      content: $svelte(<p>Another snippet here</p>)
    }
  ] }
/>

Do I understand it correctly that the recommendation would be to do this?

<Tabs
  items={ [
    {
      text: 'Tab 1',
      content: item1
    },
    {
      text: 'Tab 2',
      content: item2
    }
  ] }
/>
{#snippet item1()}
  <p>A snippet here</p>
{/snippet}
{#snippet item2()}
  <p>Another snippet here</p>
{/snippet}

@dummdidumm
Copy link
Member

Yes.

@andersekdahl
Copy link

Alright. I would definitely appreciate if there was some way to define an inline snippet in a script tag or inside svelte.[js|ts] files.

@fallaciousreasoning
Copy link
Contributor Author

Am I able to generate an array of snippets if I have an array of data?

<script lang="ts">
const items: ({ title: string, content: string })[] = [...];
</script>

<Tabs
  data={ ???}
/>

{#each items as item}
#snippet ???()
  <p>{item.content}</p>
{/snippet}
{/each}

Do I need to pass in a parameterized snippet and the data instead?

<Tabs
   items={items}
   renderContent={tabContent} />
{#snippet tabContent(item)}
  <p>{item.content}</p>
{/snippet}

I think it would definitely allow a bit more flexibility if we could define snippets inside script tags.

@dummdidumm
Copy link
Member

The second code snippet is what you would do, yes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting submitter needs a reproduction, or clarification
Projects
None yet
Development

No branches or pull requests

5 participants