4

Let's say I have a JS component - I am using Vue.JS - with a checkbox and associated label in it:

<template>
    <input id="field1" type="checkbox">
    <label for="field1">
        Some label
    </label>
</template>

Now, since we designed components to be reusable, I want to use it in multiple places of my app, at the same time. Problem: the ID is duplicated, and clicking a checkbox's label checks another checkbox since they share the same ID.

How to solve this problem?

For now I am generating a random hex ID at component mount to generate unique ID values, but it feels way too hackish.

Mickaël
  • 3,763
  • 5
  • 26
  • 32
  • If you need the checkbox to do something specific, just use `class` to wire up the event handler and don't bother with the `id` – Ryan Wilson Sep 14 '18 at 17:18
  • The ID is needed for the following `label` tag. I do use Vue's `v-model` to update the data but this was not relevant here. So I'm not sure I understand your advice :/ – Mickaël Sep 14 '18 at 17:23
  • it is not a vue problem and you just shouldn't use the same id on more than one element. – Rafael Quintela Sep 14 '18 at 17:35
  • If you already figured out a way to make the id unique, I don't understand what your problem is. – Ryan Wilson Sep 14 '18 at 17:37
  • Is it possible for you to render only distinct templates in every place of your app. I tried this in other programming and it worked for me. – Arihant Sep 14 '18 at 17:41
  • 1
    You can use this._uid in mounted() and assign this to your id, but _uid is private and might change in the future. Check example https://codepen.io/anon/pen/RYBjBP – gijoe Sep 14 '18 at 17:41
  • Possible duplicate of [Vue.js : How to set a unique ID for each component instance?](https://stackoverflow.com/questions/34950867/vue-js-how-to-set-a-unique-id-for-each-component-instance) – yuriy636 Sep 14 '18 at 17:46
  • @RyanWilson this way of generating IDs is not very elegant and is not bulletproof (slow since you need to use bits of entropy to generate a random hex string, and depending on your use-case collisions are still possible), so I was looking for a better and simpler way of doing it. – Mickaël Sep 16 '18 at 20:06
  • @RafaelQuintela I removed the vuejs tag from the question. And I do know I should not, which is why I was asking the question in the first place. – Mickaël Sep 16 '18 at 20:06
  • I misread it, sorry :P – Rafael Quintela Sep 18 '18 at 14:02

3 Answers3

9

You don't need to use an id to connect a label and an input in this specific case.

This code below achieves the same result without using HTML id

<template>
    <label>
      <input type="checkbox">
       Some label
    </label>
</template
Nishant Arora
  • 388
  • 1
  • 5
  • 10
  • 1
    Awesome, I did not know this was possible! Here is the link to the reference (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label) that states "You can nest the directly inside the – Mickaël Sep 16 '18 at 20:03
1

You need to dynamically assign IDs as prop using v-bind. This way, you will be able to set it manually each time you use your component.

Component

<template>
  <div class="field">
    <input v-bind:id="id" type="checkbox"/>
    <label v-bind:for="id">{{ label }}</label>
  </div>
</template>

<script>
export default {
  name: 'checkbox',
  props: {
    id: {
      type: String,
      required: true
    },
    label: {
      type: String,
      required: true
    }
  }
}
</script>

**Use case**
<Checkbox id="field1" label="Some text"></Checkbox>
<Checkbox id="field2" label="Another text"></Checkbox>

**Output**
<div class="field">
  <input id="field1" type="checkbox">
  <label for="field1">Some text</label>
</div>
<div class="field">
  <input id="field2" type="checkbox">
  <label for="field2">Another text</label>
</div>

[More about props.][1]
tony19
  • 125,647
  • 18
  • 229
  • 307
Quentin Veron
  • 3,079
  • 1
  • 14
  • 32
  • What if the component is deeply nested, for instance a view rendering multiple which themselves render which themselves render ? The solution does not seem viable in this case this it would mean passing the ID down just for the most inner child :/ – Mickaël Sep 16 '18 at 19:59
0

You are right. I have a component which contains 5 radio buttons. When I apply several these components on another view, duplicated id happens ! After improvement by v-bind id, duplicated id is fixed and these components perform properly .

The component is:

<template>
  <div class="container">      
      <div class="star-widget">
        <input type="radio" v-bind:name="myname" v-bind:id="myrate5" @click="starclick(5)">
        <label v-bind:for="myrate5" class="fas fa-star" :style="ratingStyle(5)"></label>
        <input type="radio" v-bind:name="myname" v-bind:id="myrate4" @click="starclick(4)">
        <label v-bind:for="myrate4" class="fas fa-star" :style="ratingStyle(4)"></label>
        <input type="radio" v-bind:name="myname" v-bind:id="myrate3" @click="starclick(3)">
        <label v-bind:for="myrate3" class="fas fa-star" :style="ratingStyle(3)"></label>
        <input type="radio" v-bind:name="myname" v-bind:id="myrate2" @click="starclick(2)">
        <label v-bind:for="myrate2" class="fas fa-star" :style="ratingStyle(2)"></label>
        <input type="radio" v-bind:name="myname" v-bind:id="myrate1" @click="starclick(1)">
        <label v-bind:for="myrate1" class="fas fa-star" :style="ratingStyle(1)"></label>        
      </div>
      <div class="rating"> {{ myrating }} </div>      
    </div>
</template>
<script>
  export default {
  name: 'Ratingstar',
  components: {
     
  },
  props: [ 'myrating', 'mymovieid', 'myrate5', 'myrate4', 'myrate3', 'myrate2', 'myrate1', 'myname' ],  
  methods: {
    starclick(i) {
      //console.log('mymovieid: ' + this.mymovieid)
      this.$emit('starclicked', i, this.mymovieid)
    }, 
    ratingStyle(j) {
      if (this.myrating > j-1) {
        return {color: '#fd4'}
      }
    }
  }
}
</script>

In another containing view, the components are duplicated according the size of movies list:

<template>
    <div>
        <h1 class="mt-4">Dashboard</h1>
    </div>
    <div class="container">
        <div class="col-md-3" v-for="movie in movies" :key="movie.imdbid">                    
            <img v-bind:src="movie.poster"  />
            <p style="text-align: center;"> {{ movie.title }} </p>  
            <p style="text-align: center;"> {{ movie.imdbid }} </p>
            <star-input 
                v-model:myrating="movie.rate" 
                v-model:mymovieid="movie.imdbid"
                v-model:myname="movie.starname"
                v-model:myrate1="movie.starid[0]"
                v-model:myrate2="movie.starid[1]"
                v-model:myrate3="movie.starid[2]"
                v-model:myrate4="movie.starid[3]"
                v-model:myrate5="movie.starid[4]"
                @starclicked="starclickHandler" />                    
        </div>
    </div>
</template>

The name, id and for are v-bind by parent view's data movies[]