DEV Community

Wade Zimmerman
Wade Zimmerman

Posted on • Updated on • Originally published at devmap.org

Vue3 Dynamic Refs

Scenario

Imagine you have multiple clickable fields which have icons. You want to focus the input field no matter where you click in the parent element.

Problem

Depending on where you click, the $event.target changes. This means that you either have to use a clever way to find the intended target or take an elegant approach.

Vue2 Solution

The most elegant approach is to define a ref and access the ref corresponding to the element being clicked.

In Vue2 this would look like the following.

<template>
  <div @click="clickMe('f1')">
    <input ref="f1" />
    <Icon/>
  </div>
  <div @click="clickMe('f2')">
    <input ref="f2" />
    <Icon/>
  </div>
  <div @click="clickMe('f3')">
     <input ref="f3" />
     <Icon/>
  </div>
</template>

<script>
 export default {
   methods: {
     clickMe(refId) {
       var input = this.$refs[refId]
       input?.focus()
     }
   }
 }
</script>
Enter fullscreen mode Exit fullscreen mode

Vue3 Problem

If you are using Vue3 composition API, you'll notice achieving this behavior is not super obvious.

Methods no longer have access to an object which holds all the refs defined in a component. So you may end up settling for a less elegant solution. However, you don't have to.

Vue3 Solution

The solution is simple. Use an object where the property values are refs. Then you could update your template to something like the following.

<script setup>
import { ref } from 'vue';

const inputs = {
    f1: ref(),
    f2: ref(),
    f3: ref(),
}

function clickMe(refId) {
  var input = inputs[refId]?.value
  input?.focus()
}
</script>

<template>
  <div @click="clickMe('f1')">
    <input :ref="inputs.f1" />
    <Icon/>
  </div>
  <div @click="clickMe('f2')">
    <input :ref="inputs.f2" />
    <Icon/>
  </div>
  <div @click="clickMe('f3')">
     <input :ref="inputs.f3" />
     <Icon/>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Bonus: A Better Way to Define the Refs

Instead of writing ref over and over. You could write a method to define refs from an array. Since I'm using Lodash, I did something like this.

function defineRefs(refs) {
  return _.chain(refs).keyBy().mapValues(ref).value();
}
Enter fullscreen mode Exit fullscreen mode

Now I can rewrite my inputs definition like this:

<script setup>

import { defineRefs } from '@/helpers'

const inputs = defineRefs(['f1', 'f2', 'f3'])

//...
</script>
Enter fullscreen mode Exit fullscreen mode

Conclusion

Instead of using an event target to focus an element, you can use dynamic refs. Doing so allows you to reuse event handlers.

Top comments (4)

Collapse
 
bubbakk profile image
Andrea Ferroni

It seams to me that this is not so dynamic. Is "multiple" refs: exactly 3.

In my mind dynamic means that the number of refs is not fixed in the code like the example given above, but generated via a :v-for directive, maybe caused by an array ajax result.

Can you extend this example in a more complete way, please?
Thank you

Collapse
 
wadecodez profile image
Wade Zimmerman

no that type of dynamic ref is impossible

Collapse
 
planet_cbx profile image
Charlène Bonnardeaux • Edited

I love your helper to define props! It's really elegant!

Collapse
 
planet_cbx profile image
Charlène Bonnardeaux

@wadecodez after testing the helper methos it seems not working, just doing a ref with a string instead html element