Skip to content

An element that uses the animate directive must be the immediate child of a keyed each block #7209

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

Open
matths opened this issue Feb 1, 2022 · 19 comments

Comments

@matths
Copy link

matths commented Feb 1, 2022

Describe the problem

The animate directive is only allowed to an immediate child of a keyed each block.

{#each todos.filter(t => !t.done) as todo (todo.id)}
	<label animate:flip>{todo.description}</label>
{/each}

As soon as the child might become more complex, the child element might be wrapped in its own component. That breaks the animate directive with the well known error message.

{#each todos.filter(t => !t.done) as todo (todo.id)}
	<Todo {todo} />
{/each}

So from a developer experience perspective, this should be possible as well because inside that component the first element is technically still an immediate child of that each block.

I prepared two REPLs.

  1. Without child component https://svelte.dev/repl/e1c1f2459ed74d96a9de082c0be01741?version=3.46.3
  2. With child component, animate directive not working https://svelte.dev/repl/b4a879ed63b64d2baaebd023d2209cc0?version=3.46.3

Describe the proposed solution

I can't fix this myself as the animate directive is resolved within the compiler I'm not so familiar with, yet.

  1. Proposed solutions is that the element in the component is recognized correctly as immediate child of the each block.
{#each todos.filter(t => !t.done) as todo (todo.id)}
	<Todo {todo} />
{/each}

And in Todo.svelte

	<script>
		export let todo;
	</script>
	<label animate:flip>{todo.description}</label>
  1. Or allow the animate directive on Components instead of elements.
{#each todos.filter(t => !t.done) as todo (todo.id)}
	<Todo {todo} animate:flip={{element}} bind:element/>
{/each}

And in Todo.svelte

	<script>
		export let todo;
		export let element;
	</script>
	<label bind:this={element}>{todo.description}</label>

Alternatives considered

Alternatives are quite tough.
As I will also use in:receive and out:send crossfade transitions, I could try to identify the remaining items of the each loop by myself and put an animation on them.

Importance

would make my life easier

@barcovanrhijn
Copy link

@matths I found a solution after facing the same:

"What you have is an indexed each block, which won't work. A keyed each block looks like this. (preferably with a proper key)"
Change your each block: {#each todos.filter(t => !t.done) as todo (todo.id)}
To this: {#each todos.filter(t => !t.done) as index (todo)}

You can then use index to set an id on the element you're creating

https://stackoverflow.com/questions/59497908/animations-in-svelte-component

@matths
Copy link
Author

matths commented Feb 15, 2022

@barcovanrhijn I'll have a look at your suggestion soon and give you feedback afterwards. Thanks for the comment.

@justin-robinson
Copy link

I just ran into the same problem. Any ideas on how we should move forward?

@3daddict
Copy link

@matths ,
I am running into a similar error

 <section use:dndzone={{ items: $store, flipDurationMs }} on:consider={handleSort} on:finalize={handleSort}>
    {#each $store as item (item.id)}
      <div id={item.id} animate:flip={{ duration: flipDurationMs }}>...</div>
    {/each}
</section>

Error:
An element that uses the animate directive must be the sole child of a keyed each block

Any ideas how to prevent this, I got the same issue trying to use index.
cc: @justin-robinson did you find a fix to your issue you were having?

@eytanProxi
Copy link

Any news ?

@bryanmylee
Copy link

bryanmylee commented Oct 7, 2022

This is a major pain point for users of Svelte Headless Table as well. The main sell is that Svelte Headless Table is truly headless so all of Svelte's native directives should work out of the box.

However, we use a <Subscribe/> helper component to auto-subscribe to non-top-level Svelte stores.

<tbody {...$tableBodyAttrs}>
  {#each $rows as row (row.id)}
    <Subscribe rowAttrs={row.attrs()} let:rowAttrs>
      <tr animate:flip={{duration: 200}} {...rowAttrs}>
        {#each row.cells as cell (cell.id)}
          <Subscribe attrs={cell.attrs()} let:attrs>
            <td {...attrs}>
              <Render of={cell.render()} />
            </td>
          </Subscribe>
        {/each}
      </tr>
    </Subscribe>
  {/each}
</tbody>

Without support for $row.attrs() in the template, <Subscribe/> is necessary. However, animate:flip throws an error because it's not the direct child of the each block, even though Subscribe does not add anything to the template.

It would be nice if animate:flip is able to detect these behaviours.

@amorfati254
Copy link

I am experiencing same issue. Did anyone find a possible solution?

@mspanish
Copy link

Same issue. Simple letter adding to a grid, without the animate, simple to add another letter once you've exhausted your initial array of letters: repl here

But if you add animate, you have to change the syntax of the #each, and once you hit the end of your list it fails to add another letter (I assume because n is no longer unique?)

repl here

@valle-xyz
Copy link

+1

@space-nuko
Copy link

Is there no way around this? It means I have to update the parent of the child widget I want the state to update in a list, so the entire parent container tree has to be rerendered, because I can't move my state contained in the direct child div wrapping the component into the component itself

@Gerark
Copy link

Gerark commented Sep 22, 2023

+1

@justin-robinson
Copy link

@3daddict I did not find a solution and moved away from Svelte. An issue like this tells me Svelte is not ready for my use case.

@pablohernandezm
Copy link

Why not use your component inside a div 👀

@Gerark
Copy link

Gerark commented Oct 3, 2023

Why not use your component inside a div 👀

That's how I fixed it but it feels a lot like a workaround to me.

@seanodotcom
Copy link

Why not use your component inside a div 👀

This breaks the layout when using with a table (trying to animate components in flowbite-svelte, e.g.)

@Git002
Copy link

Git002 commented Mar 14, 2024

It's 2024 and things are still not solved 😭😭😭

{#each column.items as item (item.id)}
    <div>
        {#if foundIds.includes(item.id)}
            <div class="card" animate:flip={{ duration: flipDurationMs }}>
                {item.name}
                {item.id}
              </div>
        {:else}
             <div></div>
        {/if}
    </div>
{/each}

Error: An element that uses the animate directive must be the immediate child of a keyed each blocksvelte(invalid-animation)

@Rich-Harris Rich-Harris added this to the 5.x milestone Apr 2, 2024
@Stijn-Timmer
Copy link

Stijn-Timmer commented Apr 24, 2024

It's 2024 and things are still not solved 😭😭😭

{#each column.items as item (item.id)}
    <div>
        {#if foundIds.includes(item.id)}
            <div class="card" animate:flip={{ duration: flipDurationMs }}>
                {item.name}
                {item.id}
              </div>
        {:else}
             <div></div>
        {/if}
    </div>
{/each}

Error: An element that uses the animate directive must be the immediate child of a keyed each blocksvelte(invalid-animation)

Had the same, i made a (very ugly) solution.

//Bind this to something to search a list of cards
let query: string = ''

{#each column.items as item (item.id)}
    <div class="card hidden" animate:flip={{ duration: flipDurationMs }} class:show={query || item.name}>
        {item.name}
        {item.id}
    </div>
{/each}

<style>
    .show {
       @apply block;
    }
</style>

I utilized a conditional to apply a class, allowing me to use CSS to toggle the visibility of the card. Since I'm using Tailwind CSS, you might need to define the classes to suit your requirements. Hope this solution is helpful for you.

@orefalo
Copy link

orefalo commented Aug 29, 2024

+1 same issue

@imstilltrying
Copy link

imstilltrying commented Dec 22, 2024

Yeah, this needs to get bumped up in the issues. Can't believe it's been 2 years since raised. Can't find a workaround at all with a table {#each} then Component when the Component has the tr. Tried to refactor to pull the tr out of the Component, but that has its own problems, see below:

{#each tasks as task, index (task.id)}
{#if !(!showcompletetasks && task.is_complete)}
<tr animate:flip={{ duration: 200 }}>
<Task/>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests