Filipe Scaglia
Filipe Scaglia

Reputation: 76

Vue changes to array not updating the DOM

I'm trying to make changes to an array in the parent component (insert and update data) that is passed to the child component via Props, but the DOM is not being updated.

Parent component:

<UsersList
  v-for="(role, i) in userRolesNames"
  :key="i"
  :users="usersPages[i].data"
/>

Child component:

<template>
<div
  v-for="user in users"
  :key="user.id"
>
  <span>{{ user.name }}</span>
  <div>
    <i
      class="bi bi-pen-fill edit-icon m-pointer"
      @click="onClickEdit(user)"
    ></i>
    <i
      class="bi bi-trash-fill delete-icon ms-2 m-pointer"
      @click="onClickDelete(user)"
    ></i>
  </div>
</div>
</template>
<script lang="ts">
// recieving the array
props: {
  users: {
    required: true,
    type: Array as PropType<usersType[]>
  }
}
</script>

Each user basically has the following structure:

{
  id: x,
  name: 'x',
  email: 'x',
  login: 'x',
  role: x
}

The problem is that when trying to insert or update a record in the usersPages[i].data array, the DOM doesn't change. If I use the Vue developer tools, the data is changing correctly, but the DOM isn't.

Tried inserting using the push method on the array but without success. The only thing that worked was this:

const newUser = response.data;
const page = this.usersPages[newUser.role - 1];
Vue.set(page, 'data', [...page.data, newUser]);

To update I tried to directly change the user properties, but like the insert the DOM doesn't update. What worked was:

const page = this.usersPages[user.role - 1];
const oldUsers = page.data.filter(u => u.id != user.id);
Vue.set(page, 'data', [ ...oldUsers, response.data ]);

Works but doesn't look right... Can anyone help?

Upvotes: 4

Views: 4087

Answers (3)

Filipe Scaglia
Filipe Scaglia

Reputation: 76

I decided to create an example to show @peperoneen how I did it, as I believed the way was correct. But in the example itself, it worked and in my project, it didn't, and the only difference was the way the list was initially filled.

To load the users I do a request to the API that returned the paged data, which I normally assigned with the '=' operator. However, I decided to do the assignment step by step and in the users part, I used the 'push' method. This way, the operations with the list using push and filter work normally, updating the DOM and without the need to use Vue.set

I think the problem was the way the list was being generated. I wasn't doing it reactively I guess...

Upvotes: 0

peperoneen
peperoneen

Reputation: 141

The DOM won't update because you are not doing it reactively. To update this value reactively you should listen to some event (input for example), that you'll emit in the child component and pass the new(!) value to your property. But a better way is to bind your components via v-model.

P.S. - So you can see changes in the devtools because this is not a part of the Vue lifecycle, it`s just like a parser that watches actual data, but without context.

Upvotes: 1

deirdreamuel
deirdreamuel

Reputation: 857

I had a similar problem before. I believe that this is a bug in vue. The reason for this is that Vue rendered the v-for already. Vue as of now does not know how to handle the changes in the array. What I did as a workaround for this is have a updateKey variable inside my script set to 0. Then increment this variable every time we update the array updateKey++. And we use this key for our component.

On child component,

<template>
<span
  v-for="user in users"
  :key="user.id"
>
  <div :key="updateKey">
    <span>{{ user.name }}</span>
    <div>
      <I
        class="bi bi-pen-fill edit-icon m-pointer"
        @click="onClickEdit(user)"
      ></i>
      <I
        class="bi bi-trash-fill delete-icon ms-2 m-pointer"
        @click="onClickDelete(user)"
      ></i>
    </div>
  </div>
</span>
</template>

and onClickEdit(user) and onClickDelete(user) make sure you have updateKey++

Upvotes: 2

Related Questions