Search code examples
vue.jsvuejs3vue-clivue-composition-api

Vue 3 & Composition API : template refs in v-for loop error : only get proxies


I've been developing my first project with Vue3.js & Vue Cli and I've been stuck for the past couple of hours on this bit of code.

Basically what I'm trying to do is to have a list of buttons based on an array of objects created in the setup() part of the code. All objects also contain their own ref within the array itself, which I eventually bind on the template. I then make consts out of each ref so that I can use them within the setup() but when I console.log(btnConvert.value) I get a proxy, which I don't with my other refs that aren't within a v-for loop.

    RefImpl {_rawValue: Proxy, _shallow: false, __v_isRef: true, _value: Proxy}

Here's the expanded version of the console.log(btnConvert.value)

Proxy {…}
  [[Handler]]: Object
    get: ƒ get({ _: instance }, key)
    has: ƒ has({ _: { data, setupState, accessCache, ctx, appContext, propsOptions } }, key)
    ownKeys: (target) => {…}
    set: ƒ set({ _: instance }, key, value)
  [[Prototype]]: Object
  [[Target]]: Object
  [[IsRevoked]]: false

I tried everything I could think of, but I couldn't understand the official Vue doc. Could anyone help me understand how I could retrieve the DOM elements with those refs? Thank you very much !

Here's the bit of relevant code (I removed the functions to which the btn refer for ease of lecture).I can add some more if necessary.

    <template>
      <div>
        <div ref="btnList" class="options">
          <vBtn
            v-for="btn in btnArray"
            :key="btn"
            :ref="btn.ref"
            class="btn"
            :class="`btn--${btn.class}`"
            @click="btn.action"
            v-html="btn.text"
          />
      </div>
    </template>
    <script>
    import { ref, onMounted } from 'vue'
    import vBtn from '@/components/Tool/vBtn.vue'

    export default {
      components : {
        vBtn
      },

      setup() {
        const btnConvert = ref(null)
        const btnCopy = ref(null)
        const btnCancel = ref(null)
        const btnUndo = ref(null)
        const btnErase = ref(null)
        
        const btnArray = [
          {
            class: 'convert',
            text: 'some text',
            action: convertText,
            ref: btnConvert
          },
          {
            class: 'undo',
            text: 'some text',
            action: undoConvert,
            ref: btnUndo
    
          },
          {
            class: 'cancel',
            text: 'some text',
            action: cancelChange,
            ref: btnCancel
    
          },
          {
            class: 'erase',
            text: 'some text',
            action: eraseText,
            ref: btnErase
    
          },
          {
            class: 'copy',
            text: 'some text',
            action: copyText,
            ref: btnCopy
          }
        ]
    
        onMounted() {
          console.log(btnConvert.value)
          // this is where I get the proxy
        }
      },
    }
    </script>

Solution

  • I am sorry but I am not able to replicate your results

    1. I do not understand how you can get anything else then null from console.log(btnConvert.value) when you are not rendering 1st button at all thanks to v-for="btn in btnArray.slice(1)" (which effectively creates new array without the first element in source array)

    2. It just works! See below example

    Just a note:

    Could anyone help me understand how I could retrieve the DOM elements with those refs?

    Because ref is placed on Vue component (vBtn), it will never be an HTML element. It will be always a component instance...

    const app = Vue.createApp({
      setup() {
        const buttons = ['convert', 'undo', 'cancel', 'erase', 'copy']
        const action = (param) => alert(param)
        const btnArray = buttons.map((item) => ({
            text: item,
            action: action,
            ref: Vue.ref(null)
        }))
        
        Vue.onMounted(() => {
          console.log(btnArray[0].ref.value)
          console.log(btnArray)
        })
        
        return {
          btnArray
        } 
      }
    })
    
    app.mount("#app")
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.11/vue.global.js" integrity="sha512-1gHWIGJfX0pBsPJHfyoAV4NiZ0wjjE1regXVSwglTejjna0/x/XG8tg+i3ZAsDtuci24LLxW8azhp1+VYE5daw==" crossorigin="anonymous"></script>
    <div id="app">
      <button 
        v-for="(but, i) in btnArray" 
        :key="i" 
        @click="but.action(but.text)"
        :ref="but.ref"
       >  
        {{ but.text }}
       </button>
    </div>