Skip to content
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

Allow @const inside an if block #7241

Closed
blaumeise20 opened this issue Feb 10, 2022 · 15 comments · Fixed by #7451
Closed

Allow @const inside an if block #7241

blaumeise20 opened this issue Feb 10, 2022 · 15 comments · Fixed by #7451
Labels
compiler Changes relating to the compiler feature request

Comments

@blaumeise20
Copy link

Describe the problem

I want to make a {@const a = b} declaration inside an {#if cond} block, but svelte complains about it's parent.

Describe the proposed solution

It would be cool if it just worked.

Alternatives considered

None

Importance

would make my life easier

@dummdidumm dummdidumm added compiler Changes relating to the compiler feature request labels Feb 10, 2022
@bluwy
Copy link
Member

bluwy commented Feb 10, 2022

Can you show a usecase where this feature would help?

@blaumeise20
Copy link
Author

Something like this (this is very similar to my use case):

<div>
  {#if selected.length == 1}
    {@const sel = selected[0]}
    <!-- some actions on this one -->
  {:else if selected.length == 0}
    none selected
  {:else}
    multiple selected
  {/if}
</div>

@BeeMargarida
Copy link

BeeMargarida commented Feb 12, 2022

Just a possible approach for this, would the only change required be the addition of IfBlock to

const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate']);
?
Or is there something more missing?

@risalfajar
Copy link

Why don't just make it free to use without limiting it to what parent it has?
It can be useful as a local variable

@blaumeise20
Copy link
Author

It is hard to implement the way Svelte handles blocks. In the output they get compiled to different functions which are all in the same scope, and can't access the variables of a different function. The only way this could work is by passing a separate argument like consts which is basically an object containing all @const definitions.

I think it will still be harder because of reactivity, but that is most likely what is to do.

@brandonbloom
Copy link

I arrived here from a searching for a related idea: Allowing "as" clauses on if, like with each.

It would be nice to have something like {#if expression as name}...{/if}.

This would be analogous to {#each expression as name}...{/each}.

As a workaround, can do something like: {#each f(expression) as name}...{/each} where f needs a better name and would be defined: const f = (x) => x == null ? [] : [x].

A example use for this would be something like: {#if path?.to?.container?.items as items}

@blaumeise20
Copy link
Author

You should open a separate issue for this! That is a really good idea!

@dummdidumm
Copy link
Member

We won't be adding new syntax to the #if block if there's already the @const tag, so this is not going to happen (and I believe it came up before and was rejected). Allowing @const inside an #if block is the more likely way forward.

@brandonbloom
Copy link

brandonbloom commented Mar 23, 2022

@dummdidumm Just so I understand, is there some stated rationale for why not to add syntax to #if?

Not to advocate too much, but it seems like the as (name) syntax would be consistent with each and would still be a common/valuable shorthand often in preference to @const. The idea also has considerable precedence and proof of utility in many languages. For example, simple statements allowed in expression contexts in Go; anaphoric it in several languages; as pattern matching in Rust, Haskell, Swift, etc; the |pipes| "payload" unpacking in Zig; the ((double = parens)) to suppress warnings about assignments in conditions in C-style languages (including javaScript), and so on.

@7nik
Copy link

7nik commented Mar 23, 2022

@brandonbloom you can extract data you need in the <script>:

<script>
// some code
$: items = path?.to?.container?.items;
// more code
</script>

{#if items}
  <!-- your markup -->
{/if}

Avoid doing calculations and long expressions in the markup, move them to the script.

@brandonbloom
Copy link

you can extract data you need in the <script>

Yeah, of course, that's what I've been doing. I've just noticed that the lack of something like #if/as is one of the main reasons I have had to do so.

@brandonmcconnell
Copy link

you can extract data you need in the <script>

Yeah, of course, that's what I've been doing. I've just noticed that the lack of something like #if/as is one of the main reasons I have had to do so.

I can certainly see the use-case here. I hope they do add support for {@const} to if-statements soon.

In the meantime, if you’d like to move your logic to your templating and out of your JS reactivity, this should work (low-level workaround):

{@const items = path?.to?.container?.items}
{#if items}
  <!-- your markup -->
{/if}

@blaumeise20
Copy link
Author

Your example doesn't compile, for the same reason as my original example.
@const declarations are limited to some parents, so this would only work directly inside a component.

@brandonmcconnell
Copy link

brandonmcconnell commented Apr 2, 2022

@blaumeise20 Ah I see that now. I sincerely hope they do add this soon. I don't see how this could be much different from the existing implementation, as if-statements and loops both have the same block scope-treatment in JS, unless the Svelte compiler compiles {#each} blocks down to forEach loops but that would surprise me as they're not quite as performant as for..of loops.

One method I think should work (just as a temporary workaround) is transforming your {#if} into an {#each} block, like this:

{#if selected.length === 1}
  {#each selected as sel}
    one selected
  {/each}
{:else if !selected.length}
  none selected
{:else}
  multiple selected
{/if}

This should work as expected, mainly because there is only one element in the dataset being evaluated in your if-condition. If, however there are more a single element, the same logic would apply for multi-element datasets like, if you only want to use one of the items for your {@const} block.

{#if selected.length}
  {#each [selected[0]] as sel}
    any number selected, creating a const-style variable for a specific element(s) in the set

    {sel} can be used here
  {/each}
{:else}
  none selected
{/if}

This should work identically to your use case and will only iterate over, and alias, the single item you are wanting to evaluate. If you want to define multiple values as you might using destructuring within a {@const} block, you can use the same destructuring pattern within an {#each} block like this:

{#if selected.length}
  {#each [[selected[0], selected[2]]] as [val1, val2]}
    {val1} and {val2} can both be used here, in this single-iteration loop
  {/each}
{:else}
  ...

To reiterate though, I am very much in favor of adding support for {@const} blocks to {#if} blocks.

What I provided in my previous comment is merely a workaround.

@Conduitry
Copy link
Member

This is supported now in 3.48.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler Changes relating to the compiler feature request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants