Vue.js: How to set a unique ID for each component instance?
Asked Answered
R

16

184

I want to create a component with Vue.js containing a label and an input. For example :

<label for="inputId">Label text</label>
<input id="inputId" type="text" />

How can I set a unique ID for each component instance?

Rolandorolandson answered 22/1, 2016 at 16:0 Comment(2)
There are several packages/mixins which you can look into: vue-ucid, vue-component-id, vue-uniq-ids.Shindig
Having not seen the previous comment before, I also published the vue-unique-id Vue plugin for this on npm.Individuate
C
239

Each component has a unique id which can be accessed as this._uid.

<template>
  <div>
    <label :for="id">Label text for {{id}}</label>
    <input :id="id" type="text" />
  </div>
</template>

<script>
export default {
  data () {
    return {
      id: null
    }
  }, 
  mounted () {
    this.id = this._uid
  }
}
</script>

If you want more control over the ids you can for example, generate them inside a parent component.

Celestinecelestite answered 22/1, 2016 at 22:31 Comment(8)
Note that the ready method was removed in Vue 2.0 and above. I was very confused when the ready method wasn't executing. https://mcmap.net/q/137498/-vue-js-ready-function-is-not-triggeredHeddi
... and data must be a function that returns an object: vuejs.org/v2/guide/components.html#data-Must-Be-a-FunctionYajairayajurveda
Unfortunately, this does not work in TypeScript, as this._uid is not valid. Instead, generate your id yourself, e.g. public id = uuid4();, see uuid4.Ghislainegholston
I had to put the initialization in the beforeMount() method to make sure the id was set in the DOM when I tried to access it from the mounted() method.Healing
Don't use _uid, it "is reserved for internal use and it's important to keep it private (and not rely on it in user code) so that we keep the flexibility to change its behavior for potential future use cases".Shindig
For TypeScript add _uid to the interface declare module 'vue/types/vue' { interface Vue { _uid: any; } } But don't use it. Also in combination with SSR _uid and uuid4 are no options.Landers
:id="id" was very usefulMicrominiaturization
Does not work in vue3 => use buckthorn 's answer belowTogoland
S
75

To Nihat's point (above): Evan You has advised against using _uid: "The vm _uid is reserved for internal use and it's important to keep it private (and not rely on it in user code) so that we keep the flexibility to change its behavior for potential future use cases. ... I'd suggest generating UIDs yourself [using a module, a global mixin, etc.]"

Using the suggested mixin in this GitHub issue to generate the UID seems like a better approach:

let uuid = 0;

export default {
  beforeCreate() {
    this.uuid = uuid.toString();
    uuid += 1;
  },
};
Spondee answered 1/5, 2018 at 17:58 Comment(3)
a link to the pertinent GitHub issue would be very useful hereSammer
Here is the GitHub issue where Evan advise against using _id: github.com/vuejs/vue/issues/5886#issuecomment-308625735Last
this.uuid = crypto.randomUUID();Togoland
I
22

Update

I published the vue-unique-id Vue plugin for this on npm.

Answer

None of the other solutions address the requirement of having more than one form element in your component. Here's my take on a plugin that builds on previously given answers:

Vue.use((Vue) => {
  // Assign a unique id to each component
  let uidCounter = 0;
  Vue.mixin({
    beforeCreate: function() {
      this.uidCounter = uidCounter.toString();
      uidCounter += 1;
    },
  });

  // Generate a component-scoped id
  Vue.prototype.$id = function(id) {
    return "uid-" + this.uidCounter + "-" + id;
  };
});

This doesn't rely on the internal _uid property which is reserved for internal use.

Use it like this in your component:

<label :for="$id('field1')">Field 1</label>
<input :id="$id('field1')" type="text" />

<label :for="$id('field2')">Field 2</label>
<input :id="$id('field2')" type="text" />

To produce something like this:

<label for="uid-42-field1">Field 1</label>
<input id="uid-42-field1" type="text" />

<label for="uid-42-field2">Field 2</label>
<input id="uid-42-field2" type="text" />
Individuate answered 17/2, 2019 at 7:23 Comment(3)
A note that this plugin does not appear to work for Vue3Tallu
Using uuid +=1 is terrible practice. The sequence (0,1,2,3,...) is problematic enough by itself but naming it uuid is mockery of the UUID concepts.... Use uuid library with uuidv4 and you will be fine. The main advantage (apart from philosophical concepts) is that you do not need any shared storage for the current value and the code will not have artificially created sections begging for race condition issues...Jericajericho
uuid was an unfortunate name choice so I've renamed it to uidCounter. Javascript is single threaded so I don't see which race conditions could possibly creep in the code above. uidCounter is read and updated atomically because no there are no other threads and execution isn't preempted. Imo, an actual UUID doesn't offer any benefit over a simple incrementing counter for this use-case, but yes it works.Individuate
S
20

Update: Code will throw an error if ._uid property does not exist in the instance so that you can update it to use something custom or new unique id property if provided by Vue.

Although zxzak's answer is great; _uid is not a published api property. To save a headache in case it changes in the future, you can update your code with just one change with a plugin solution like below.

Vue.use({
    install: function(Vue, options) {
        Object.defineProperty(Vue.prototype, "uniqId", {
            get: function uniqId() {
                if ('_uid' in this) {
                   return this._uid;
                }
                throw new Error("_uid property does not exist");
            }
        });
    }
});
Summitry answered 29/4, 2017 at 7:25 Comment(3)
This is still using the uid, which in your own answer acknowledges is advised against. Please don't post answers advocating bad practices. This answer should be removed.Hardened
Yes but in case the published api is changed / removed, they will have to change only one place in the entire code. In the other answer, it was per component. I already emphasized that in the title.Summitry
Also, I just updated the code so that it will throw an error in case _uid property does not exist anymore.Summitry
A
16
npm i -S lodash.uniqueid

Then in your code...

<script>
  const uniqueId = require('lodash.uniqueid')

  export default {
    data () {
      return {
        id: ''
      }
    },
    mounted () {
       this.id = uniqueId()
    }
  }
</script>

This way you're not loading the entire lodash library, or even saving the entire library to node_modules.

Alcuin answered 17/1, 2020 at 16:36 Comment(2)
lodash.uniqueid is lightweight package and easy to use. (MINIFIED: 857B)Rheumy
This answer seems incomplete. Don't you have to bind the ID on the component somehow? MDN's tutorial gives a similar answer here, but that just generates warnings and no id properties on elements. Also, -S (or --save) has been the default on npm since 2017 and doesn't actually do anything any more.Ambulacrum
P
14

For Vue.js v3 you can get id like this:

In template: {{ $.uid }}

In script: this.$.uid

Or use your own function or mix them:

this.componentUid = ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
          (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
        );

This will return e.g:

aa174375-5b75-4919-acd0-980fcd54003c

Perversity answered 2/5, 2022 at 7:49 Comment(0)
J
5

The simplest way I found was to create a UUID (uuid package) manually through a global mixin. That way you won't rely on anything that can potentially change or become deprecated in the future like this._uid.

You first have to install the uuid package:

npm i uuid

Then, in your main.js file create a global mixin:

// rest of imports

import { v4 as uuidv4 } from 'uuid';

const app = Vue.createApp(App);

app.mixin({
    data() {
        return {
            componentId: uuidv4()
        }
    },
});

app.use(store).use(router).mount('#app');

And here is how you can us it in a component:

<template>
   <div>
      <h1>{{ componentId }}</h1>
      <button @click="printId()">click me for componentId.</button>
   </div>
</template>

<script>
export default {
   methods: {
      printId: function() {
         console.log(this.componentId);
      }
   }
}
</script>
Jalbert answered 30/7, 2021 at 11:49 Comment(0)
S
4

This seem to work for me using in nuxtjs

https://www.npmjs.com/package/uuid

example of generated output: element: 47bfe557-d75f-455c-9a37-85b7935b297b

package.json

"dependencies": {    
    "uuid": "^8.3.2"
 },

on child component, might not be the best way but seem to work

...

<ComponentName v-if="element" />

...

import { v4 as uuidv4 } from 'uuid';

...

data() {
  return {
    element: null,
  }
}

...

mounted() {
  this.element = uuidv4();
}
Scrummage answered 4/3, 2021 at 5:7 Comment(0)
S
2

In Vue2, use v-bind.

Say I have an object for a poll

<div class="options" v-for="option in poll.body.options">
  <div class="poll-item">
    <label v-bind:for="option._id" v-bind:style="{color: option.color}">
      {{option.text}}
    </label>
    <input type="radio" v-model="picked" v-bind:value="option._id" v-bind:id="option._id">
  </div>
</div>
Solvent answered 6/3, 2017 at 23:10 Comment(1)
What you should go for is v-for="(option, index) in poll.body.options", and use index in you v-bind.Buiron
W
2

A simple approach that I haven't seen in the replies is:

<template>
  <div>
    <label :for="id">Label text for {{id}}</label>
    <input :id="id" type="text" />
  </div>
</template>

<script>
import uniqueId from 'lodash-es/uniqueId'

export default {
  computed: {
    id () {
      # return this._uid
      return uniqueId('id')
    }
  }
}
</script>
Witherspoon answered 26/12, 2018 at 9:40 Comment(3)
The creator of Vue.js says you should avoid using _uid because it's for internal use and someday they may remove it or rename it or change its behaviour.Brindle
Thanks, I think that is correct. I have updated the code with a different solution, hopefully still simple enough. Anyway, the idea of this example was to use a computed property.Witherspoon
uniqueId from lodash is the best approach in my opinionDesolate
D
2

If you're using TypeScript, without any plugin, you could simply add a static id in your class component and increment it in the created() method. Each component will have a unique id (add a string prefix to avoid collision with another components which use the same tip)

<template>
  <div>
    <label :for="id">Label text for {{id}}</label>
    <input :id="id" type="text" />
  </div>
</template>

<script lang="ts">
  ...
  @Component
  export default class MyComponent extends Vue {
    private id!: string;
    private static componentId = 0;
    ...
    created() {
      MyComponent.componentId += 1;
      this.id = `my-component-${MyComponent.componentId}`;
    }
</script>
Dasher answered 3/4, 2020 at 10:42 Comment(1)
What is the equivalent for the non-class-based syntax for defining Vue components? For example using `export default defineComponent({ created() { ... }, ... });Bruce
G
1

This package seems to be a good solution for the underlying issue of having non-unique IDs in your DOM across multiple components:

vue-uniq-ids

It is a trend to use components. Components are cool, they are small, obvious, easy to use and modular. Untill it comes to the id property.

Some HTML tag attributes requires using an id property, like label[for], input[form] and many of aria-* attributes. And the problem with the id is that it is not modular. If several id properties on the page will has the same value they can affect each other.

VueUniqIds helps you to get rid of this problem. It provides the set of id-related directives which value is automatically modified by adding unique string while keeping the attrbitue easy to read.

Galarza answered 26/7, 2019 at 20:47 Comment(0)
S
0

This seem to work for me using https://www.npmjs.com/package/uuid

example of generated output: element: 47bfe557-d75f-455c-9a37-85b7935b297b

package.json

"dependencies": {    
    "uuid": "^8.3.2"
 },

component.vue

v-if="element"

...

import { v4 as uuidv4 } from 'uuid';

...

data() {
  return {
    element: null,
  }
}

...

mounted() {
  this.element = uuidv4();
}
Scrummage answered 4/3, 2021 at 5:2 Comment(0)
B
-2

According to MDN, you can just make an implicit label binding.

<label>
  Label text
  <input type="text" />
</label>

This way you don't even need to assign an id.

Beesley answered 23/11, 2021 at 22:5 Comment(2)
In your example, you don't need an id property, but this doesn't really answer the question. There are cases in which you can't avoid needing to use an id. Also, MDN has a Vue tutorial (here) where they give an non-working example of how to set unique IDs on components... That's what brought me here.Ambulacrum
This does not relate directly to the issue at hand, but... while implicit ids are technically valid html, WCAG H44: Using label elements to associate text labels with form controls strongly recommends using the for attribute to explicitly associate labels with controls.Spondee
A
-8

If your uid is not used by other compoment, I have an idea.

uid: Math.random()

Simple and enough.

Astigmia answered 21/2, 2019 at 3:28 Comment(8)
It's difficult to justify this approach when there is a real chance of id collision...Babu
@Babu well - with large enough space of possible values it is much better than some "solutions" using +=1.Jericajericho
@RadekHladík The += 1 method is actually going to be far more robust if the id only needs to be unique within the running instance of the application. You'll never get a collision as the page will no doubt be refreshed far before running out of numbers. The random method could have the ids duplicated as soon as the second id, and would also work for far less time due to having access to less numbers...Babu
@shadow I do not want to be offensive but you have no idea what you are talking about. In JS the Math.Random returns the same type (Number). So it is only up to implementation if the Random will use its full range (cca 2^53). Or you can use the uuid which is 128bit long. Collision with even 2^32 is so unprobable that it is not important. However the biggest problem is that is is not about the running out of number but about the fact, that you can generate two same ids by accident - i.e. using the id from last page load in combination with the new id.Jericajericho
@Babu but the second problem is that you are creating a code that can not be paralelized. I know that JS is a single thread environment but it is considered a bad habit to create unnecessary limitations in your code - i.e. when it is not needed by the problem that you are trying to solve. Now it may seem fine but later the "developer" may think that the top button on the page has ID of 5 (because it has always had) and then you run it in different browser or add another id somewhere and it will change. Basically do not impose unnecessary limitations on your code if you do not have to...Jericajericho
@radekhladik for starters, math.random returns a float between 0 and 1. So no, the full range of Number is not available even if they housed in the same type. Second, as you noted, JavaScript is a single thread environment. Sure, this will not work as expected in a multi threaded environment, but to account for that in this one is overengineering.Babu
@shadow You should probably learn, how float numbers work... There is a thing called exponent that makes it possible to use the whole range even with numbers between 0-1. Or put simply: it can either have bigger accuracy on small range or hold larger range with less accuracy...Jericajericho
@RadekHladík I think at this point, it's best to agree that we disagree. We've made our points in the comments - readers can decide for themselves.Babu
R
-11

It can also be achieved using this pattern (Vue 2.0 v-bind) , so let say you have a list of items to iterate over and you want to give some dom element uninque id's.

new Vue({

  el:body,
  data: {
     myElementIds : [1,2,3,4,5,6,8]
   }
})

Html

<div v-for="id in myElementIds">
    <label v-bind:for="id">Label text for {{id}}</label>
    <input v-bind:id="id" type="text" />
<div> 

Hope it helps

Rivas answered 2/3, 2017 at 17:7 Comment(1)
You are simply defining the ID in the array. Doesn't solve the original question at all.Yajairayajurveda

© 2022 - 2024 — McMap. All rights reserved.