Home > Software design >  Vue 3 template refs does not really reflect actual DOM state
Vue 3 template refs does not really reflect actual DOM state

Time:11-11

I am creating a website for planning out study timetables.

I am now creating an Add/Remove subject section where user can add, edit or remove the Subject containing id and name, all the subjects user have added will be represented as a list of <input>s so users can easily edit them, and stored as an array of objects (Array<{ id: string, name: string }>).

I have some criteria that I wanted the list to behave when user edits the key (subject ID) whether the added one or the one they're adding.

  • If you add new Subject with the same id but different name, you will be editing that name in the list.
  • If you edit the Subject that you've added to the array. When you change its id, if the id that you've changed is the same as the other one, the old one will get deleted. for example.
const subjectList = [
  { id: 'B2205', name: 'Business 1' },
  { id: 'B2210', name: 'Internship' },
  { id: 'U1539', name: 'Undergraduate Thesis' }
]
// User edits the id from B2210 to B2205, the array will become
const subjectList = [
  { id: 'B2205', name: 'Internship' },
  { id: 'U1539', name: 'Undergraduate Thesis' }
]

The problem that I am having is that I am trying to implement a feature where on the second criteria. When the id is changed and the duplicate is deleted. I wanted to call HTMLInputElement.focus() on the <input> so users can continue editing without leaving the keyboard.

I approached the problem by using Template Refs with v-for. I created 2 variables called subjects storing Subject object that users have created, and subjectRefs to act as reference to each <input> that I'm going to generate using v-for. This solution works as expected when adding new subject. but when I delete the subject. value in subjects gets deleted but in subjectRefs, it does not get deleted. It stays the same length as before. I logged out the value and sees this.

// Initial
subjects | subjectRefs
[]         []

// Add { id: 'A1', name: 'A' }
subjects                   | subjectRefs
[                          | [
  { id: 'A1', name: 'A' }  |   HTMLInputElement.value = 'A1'
]
                          | ]
// Add { id: 'B1', name: 'B' }
subjects                   | subjectRefs
[                          | [
  { id: 'A1', name: 'A' }  |   HTMLInputElement.value = 'A1'
  { id: 'B1', name: 'B' }  |   HTMlInputElement.value = 'B1' 
]                          | ]

// Remove { id: 'B1', name: 'B' }
subjects                   | subjectRefs
[                          | [
  { id: 'A1', name: 'A' }  |   HTMLInputElement.value = 'A1'
]                          |   HTMlInputElement.value = 'B1' 
                           | ]

// Add { id: 'C1', name: 'C' }
subjects                   | subjectRefs
[                          | [
  { id: 'A1', name: 'A' }  |   HTMLInputElement.value = 'A1'
  { id: 'C1', name: 'C' }  |   HTMlInputElement.value = 'C1' 
]                          | ]

// Remove { id: 'A1', name: 'A' }
subjects                   | subjectRefs
[                          | [
  { id: 'C1', name: 'C' }  |   HTMLInputElement.value = 'C1'
]                          |   HTMlInputElement.value = 'C1'
                           | ]

// Add { id: 'A1', name: 'A' } and { id: 'B2', name: 'B' }
subjects                   | subjectRefs
[                          | [
  { id: 'C1', name: 'C' }  |   HTMLInputElement.value = 'C1'
  { id: 'A1', name: 'A' }  |   HTMlInputElement.value = 'A1'
  { id: 'B2', name: 'B' }  |   HTMLInputElement.value = 'B2'
]                          | ]

// Edit { id: 'C1', name: 'C' } to { id: 'C9', name: 'C' }
subjects                   | subjectRefs
[                          | [
  { id: 'C9', name: 'C' }  |   HTMLInputElement.value = 'C9'
  { id: 'A1', name: 'A' }  |   HTMlInputElement.value = 'A1'
  { id: 'B2', name: 'B' }  |   HTMLInputElement.value = 'B2'
]                          | ]

// Remove { id: 'C9', name: 'C' } and { id: 'A1', name: 'A' }
subjects                   | subjectRefs
[                          | [
  { id: 'B2', name: 'B' }  |   HTMLInputElement.value = 'B2'
]                          |   HTMlInputElement.value = 'B2'
                           |   HTMLInputElement.value = 'B2'
                           | ]

It doesn't look like the subjectRefs are updating its value, I don't know if manually manipulating the ref array will be bad thing or not.

I have provided a codesandbox for you guys here but the logs won't let you see that deep. If you have any more questions or requirements please let me know. Thank you.

CodePudding user response:

Probably you can change your removeSubject function like this i.e add code to remove from subjectRefs as well

   const removeSubject = (index) => {
      try {
        subjects.value.splice(index, 1);
        subjectRefs.value.splice(index, 1);
      } catch (e) {
        throw new Error("Cannot convert 'key' to number.");
      }
      console.log(subjects.value, subjectRefs.value);
    };

I guess you were expecting that it would be automatically removed on re render, but that doesn't happen in your code. Have a look in comments in your code below

 <div
      v-else
      
      v-for="(subject, i) in subjects" <!--- rerenders everytime subjects changes However vue will not re-render those elements for which key is same -->
      :key="`render-subject-${i}`"
    >
      <input
        
        type="text"
        :ref="
          (el) => {
            if (el != null) subjectRefs[i] = el; <!--- Once set, none of its entry will be deleted unless done manually -->
          }
        "
        :value="subject.id"
        @input="editSubject(i, $event?.target.value, 'ID')"
      />
      <input
        
        type="text"
        :value="subject.name"
        @input="editSubject(i, $event?.target.value, 'ID')"
      />
      <button  @click="removeSubject(i)">
        Remove
      </button>
    </div>
  </div>
  • Related