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

Conditional bind:this (allow undefined and conditional targets) #4570

Closed
dkzlv opened this issue Mar 17, 2020 · 8 comments
Closed

Conditional bind:this (allow undefined and conditional targets) #4570

dkzlv opened this issue Mar 17, 2020 · 8 comments
Labels
awaiting submitter needs a reproduction, or clarification feature request temp-stale

Comments

@dkzlv
Copy link
Contributor

dkzlv commented Mar 17, 2020

Is your feature request related to a problem? Please describe.

It has a lot of possible applications. Here's the latest problem I faced today.

I have a big paginated list, that has page buttons in the bottom of the list, and I want to scroll the list to the first item whenever I change the page. I want to use scrollIntoView method, so I need a ref to the first item of the list.

Describe the solution you'd like

It seems to be quite safe to allow undefined as a possible value for ref — it would just mean to not bind this element/component to any variable.

{#each arr as item, index}
  <p bind:this={index === 0 ? ref : undefined}>{item}</p>
{/each}

It also seems legit from developer point of view to allow dynamic ref target with code like this:

{#each arr as item, index}
  <p bind:this={cond(item) ? ref : ref2}>{item}</p>
{/each}

Describe alternatives you've considered
Right now I do this in multiple ways.

Array of refs. REPL. In two words:

<script>
  let refs = [];
</script>

{#each arr as item, index}
  <p bind:this={refs[index]}>{item}</p>
{/each}

and then use refs[0] or refs[refs.length - 1] to get certain elements.

I think:

  1. it is confusing
  2. it might have performance implications with huge lists
  3. it can even break business logic, because if your array gets smaller over time you'll end up with a bunch of null values in it. REPL

Custom class name. Assign a conditional class via class:custom-name={index === 0}, and then use document.querySelector to get the element with this class. It is bad because of two reasons: class name clashes (need to come up with unique name) and class name duplication (one in <script> and one in layout).

How important is this feature to you?
Not that much, it just seems like a good feature to have. I'm not aware of the internals of Svelte, but I assume adding any kind of dynamic code in element binding would be a lot of pain, because it most probably happens during compile time and not in the runtime.

Additional context
Same ideas were mentioned in this comment.

@Conduitry
Copy link
Member

If the expression inside the bind:this={...} isn't assumed to be directly usable as the left hand side of an assignment, I'm not sure that there's a clear way to handle this. How do we know what level to 'stop' at when evaluating this and to assume is to be treated as the LHS of an assignment? What would bind:this={[a[x], a[y], a[z]][k]} do? Bind to index k in this array literal? Bind to either a[x], a[y], or a[z] depending on the value of k?

@Conduitry Conduitry added the awaiting submitter needs a reproduction, or clarification label Mar 28, 2020
@dkzlv
Copy link
Contributor Author

dkzlv commented Mar 28, 2020

As I said, the second option, that lets you swap target variables, can be vague. I have yet to find a feasible use case for this feature, tbh.
The first option is quite feasible to me though. undefined logic seams pretty straightforward.

What would bind:this={[a[x], a[y], a[z]][k]} do? (1) Bind to index k in this array literal? (2) Bind to either a[x], a[y], or a[z] depending on the value of k?

This example is a bit artificial, but I would say the second option should work.
It's just like how it works in all the vdom frameworks. I'm not aware of the internals of Svelte, so it might be pain to implement, but I would say it should be quite obvious for a developer.

@davidcallanan
Copy link

I am trying to do the following, any ideas for other solutions?

<script>
  const items = [...];
  
  let selectedIndex = 3;
  let selectedElement;

  $: selectedElement, (() => {
    selectedElement.getClientBoundingRect().doSomeStuff();
  })();
</script>

{#each items as item, i}
  <div class="item" class:selected={selectedIndex == i} bind:this={selectedIndex == i ? selectedElement : undefined}/>
{/each}

@dkzlv
Copy link
Contributor Author

dkzlv commented Aug 31, 2020

@davidcallanan Simplest solution for now would be to store all the refs and only use the one you need.

<script>
  const items = [...];
  
  let selectedIndex = 3;
  let refs = [];

  $: refs[selectedIndex]?.getClientBoundingRect().doSomeStuff();
</script>

{#each items as item, i}
  <div class="item" class:selected={selectedIndex == i} bind:this={ref[i]}/>
{/each}

@kamrade
Copy link

kamrade commented Mar 3, 2024

Does anyone know if anything has changed since then?

@artm
Copy link

artm commented Apr 3, 2024

it haven't. trying to bind index === 0 ? refs : undefined with Svelte 4.2.12 produces the error Can only bind to an identifier (e.g. foo) or a member expression (e.g. foo.bar or foo[baz])

@artm
Copy link

artm commented Apr 7, 2024

I have the following use case and workaround:

<script>
    export let paras
    let curSegSpan;
    $: currentSeg = ... // some logic to find the current segment
    $: if (curSegSpan) {
        // some dom javascript to apply to current segment
    }
</script>

<div class="stream">
    {#each paras as segments}
        <p>
            {#each segments as segment}
                {@const current = segment === currentSeg}
                {#if current}
                    <Segment {segment} {current} bind:span={curSegSpan} />
                {:else}
                    <Segment {segment} {current} />
                {/if}
            {/each}
        </p>
    {/each}
</div>

I could't use @dkzlv's workaround (array of bound elements) because the segments, one of which may be current, don't come from a flat array, but from an array of arrays. I wish there was some shortcut syntax for bind:span={ curSegSpan } only when current is true.

@Conduitry
Copy link
Member

This can be handled in v5 with function bindings.

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 feature request temp-stale
Projects
None yet
Development

No branches or pull requests

7 participants