How to implement <transition-group> inside a v-for loop?

725 views Asked by At

There's one suggested answer on this, but the solution isn't working for me. I have a nested v-for and would like to animate the innermost li elements as they are removed or added by my computed statement. My current code looks like so:

    <transition-group @before-enter="beforeEnter" @enter="enter" @leave="leave" tag="ul" v-if="computedProviders">
      <li v-for="(letter, index) in computedProviders" :key="index">
        <div>
          <p>{{index.toUpperCase()}}</p>
        </div>
        <transition-group :letter="letter" tag="ul" class="list" @before-enter="beforeEnter" @enter="enter" @leave="leave">
          <li v-for="provider in letter" :key="provider.last_name">
            <div>
              <a :href="provider.permalink">
                {{provider.thumbnail}}
              </a>

              <div>
                <h3>
                  <a :href="provider.permalink">
                    {{provider.last_name}}, {{provider.first_name}} <span>{{provider.suffix}}</span><br>
                    <p>{{provider.specialty}}</p>
                  </a>
                </h3>
              </div>
            </div>
          </li>
        </transition-group>
      </li>
    </transition-group>
  </div>

The outer transition-group works fine, but when I set up the inner one I get

ReferenceError: letter is not defined.

I tried adding :letter="letter" as suggested here, but it's still not working for me. Any suggestions? I'm happy to reformat the code if there's a way that makes better sense.

Edit: in response to a couple of the comments here, first of all, I'm injecting Vue into a PHP-based Wordpress template, so I'm not able to create separate components. I don't know if that's part of what's causing the issue or why some of you can run the code with no errors.

Here's a sample of the JSON this is iterating over:

{
   a: [
       {
        first_name: 'John',
        last_name: 'Apple',
        suffix: 'DDS',
        permalink: 'www.test.com',
        thumbnail: '<img src="test.com" />',
        specialty: 'Some specialty'
       }, 
       {
        first_name: 'Jane',
        last_name: 'Apple',
        suffix: 'DDS',
        permalink: 'www.test.com',
        thumbnail: '<img src="test.com" />',
        specialty: 'Some specialty'
       }
      ],
    d: [
       {
        first_name: 'John',
        last_name: 'Doe',
        suffix: 'DDS',
        permalink: 'www.test.com',
        thumbnail: '<img src="test.com" />',
        specialty: 'Some specialty'
       }, 
       {
        first_name: 'Jane',
        last_name: 'Doe',
        suffix: 'DDS',
        permalink: 'www.test.com',
        thumbnail: '<img src="test.com" />',
        specialty: 'Some specialty'
       }
      ]
}
2

There are 2 answers

2
tony19 On BEST ANSWER

One of the caveats with using in-DOM templates is that the browser parses the DOM before Vue gets to it. The browser doesn't know what <transition-group tag="ul"> is, so that just gets ignored. Instead, it sees an <li> inside another <li>. Since <li> elements normally cannot be nested, the browser hoists <li v-for="provider in letter" :key="provider.last_name"> outside its parent <li> that defines letter.

Removing Vue and inspecting the DOM will reveal the problem:

hoisted DOM

When Vue processes that template, it encounters letter, which is technically undeclared outside of the v-for, resulting in the error you're seeing.

Solution 1: <template> wrapper

If you need to use in-DOM templates, wrap the inner <transition-group> with a <template> so that the browser will ignore it:

<transition-group tag="ul">
  <li v-for="(letter, index) in computedProviders" :key="index">
    <template> 
      <transition-group tag="ul">
        <li v-for="provider in letter" :key="provider.last_name"></li>
      </transition-group>
    </template>
  </li>
</transition-group>

demo 1

Solution 2: String templates

Move the template into a string (using the template option), which avoids the DOM parsing caveats:

new Vue({
  template: 
    `<transition-group tag="ul">
       <li v-for="(letter, index) in computedProviders" :key="index">
         <transition-group tag="ul">
           <li v-for="provider in letter" :key="provider.last_name"></li>
         </transition-group>
       </li>
     </transition-group>`
  //...
}).$mount('#providers')

demo 2

1
geauser On

Don't really know what's causing the error, but try creating a new component that accepts letter as a prop and contains your inner <transition-group>. Then embed this new component in your root v-for.

It should give something like that:

<transition-group @before-enter="beforeEnter" @enter="enter" @leave="leave" tag="ul" v-if="computedProviders">
  <li v-for="(letter, index) in computedProviders" :key="index">
    <div>
      <p>{{index.toUpperCase()}}</p>
    </div>
    <my-component :letter="letter" />
  </li>
</transition-group>