0

I have a parent and a child component. The parent component "invitations" contains multiple child components of "invitation". When I refresh the website all of the invitations are shown on the website, but as soon as I update the invitations array (adding an invitation) it isn't shown on the page even though I can see the array being updated on the vue developer console. I have to refresh the page to see it.

It looks like this when something has been added to the array (updated): see here

It looks like this when I refresh the page after it has been added to array.

see here

see here

Here are my two components:

invitations.vue

<template>
<li class="nav-item dropdown" id="invitations">
    <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
       data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
        Invitations <i class="far fa-bell"></i>
    </a>

    <div class="dropdown-menu" style="min-width: 20rem" aria-labelledby="navbarDropdown" 
    id="dropdownMenu">

        <div v-for="invitation in invitations" v-bind:key="invitation.id" v- 
    bind:invitations="invitations">

            <Invitation :invitation="invitation" v-on:accept-invitation="accept" v-on:reject- 
    invitation="reject" v-bind:invitation="invitation"></Invitation>

        </div>
    </div>
</li>
</template>

<script>
import Invitation from "./Invitation";

export default {
    name: "Invitations",
    props: {
        invitations: {
            type: Array,
            default: [],
            required: true
        },
        user: {
            type: Object,
            required: true
        }
    },
    methods: {
        accept(team_id) {
            axios.get('/request/accept/' + team_id)
                .then((response) => {
                    this.invitations = this.invitations.filter(invitation => invitation.id)
                });
        },
        reject(team_id) {
            axios.get('/request/reject/' + team_id)
                .then((response) => {
                    this.invitations = this.invitations.filter(invitation => invitation.id)
                });
        },
        handleIncoming(invitation) {
            this.saveNewInvitation(invitation);
            return;
        },
        saveNewInvitation(invitation) {
            this.invitations.push(invitation);
        },
    },
    mounted() {
        Echo.channel('invitations.' + this.user.id)
            .listen('NewInvitation', (e) => {
                this.handleIncoming(e);
            });
    },
    components: {Invitation}
}
</script>

invitation.vue

<template>
<div :id="invitation.id" class="mb-2">
    <a class="ml-4" title="Show team info" style="margin-right: 90px"
       :href="'/team/index/' + invitation.id" v-bind:name="invitation.name">{{invitation.name}}</a>

    <a class="float-right mr-3" href="#" @click="$emit('reject-invitation', invitation.id)">
        <i class="fas fa-times reject" :teamId="invitation.id"></i>
    </a>

    <a class="float-right mr-3" href="#" @click="$emit('accept-invitation', invitation.id)">
        <i class="fas fa-check accept" :teamId="invitation.id"></i>
    </a>
</div>
</template>

<script>
export default {
    name: "Invitation",
    props: {
        invitation: {
            type: Object,
            default: null
        }
    }
}
</script>

Can someone tell me what's wrong?

Umut Savas
  • 113
  • 1
  • 14
  • why do you have `:invitation="invitation" .... v-bind:invitation="invitation"` - since `:invitation` is just shorthand for `v-bind:invitation` - i.e. they are the same thing ... not saying that's the issue though – Jaromanda X May 23 '20 at 00:43
  • I tried to solve it with v-bind but it didn't work. But even without, it doesn't work. – Umut Savas May 23 '20 at 00:45
  • the props are objects so might need a deep watcher on the child component – Karl L May 23 '20 at 01:03
  • what's this supposed to do: `this.invitations = this.invitations.filter(invitation => invitation.id)` – João Paulo Macedo May 23 '20 at 01:37
  • its supposed to remove the invitation from the list when you accepted or rejected the invitation. – Umut Savas May 23 '20 at 01:46

1 Answers1

1

I'm not 100% which function it is that you're specifically using to modify an array, but one place where I notice a potential hang-up is in...

saveNewInvitation(invitation) {
  this.invitations.push(invitation);
}

In the Vue documentation (link to "Reactivity in Depth -- For Arrays") Vue specifically warns that it will not notice mutations to the underlying array.

Vue cannot detect the following changes to an array:

When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue

When you modify the length of the array, e.g. vm.items.length = newLength

Although .push() may not immediately trigger alarm, when you read the documentation for the push method (link to MDN Docs), you notice that it is mutating the array, not creating a new one.

The push() method adds one or more elements to the end of an array and returns the new length of the array. [emphasis added]

As seen in this prior Stack Overflow question (link to discussion), the internal workings of the push method only do two things -- change the length, and set an item by index -- which are the only two things Vue can't see.

One method that will create a new Array object is concat():

saveNewInvitation(invitation) {
  this.invitations = this.invitations.concat(invitation)
}

...because, as mentioned in the docs for concat() (link to MDN Docs):

Return value: A new Array instance.

...which Vue will properly see.

tony19
  • 125,647
  • 18
  • 229
  • 307
Lanny Bose
  • 1,811
  • 1
  • 11
  • 16