3

I am new to Vue and I am struggling with an extremely weird issue: In my component, in the creates() function I fetch data and save them into my items Array. I display the content of the items with v-for as a paragraph.

Now here comes the weird thing: When I start the App, nothing shows, although I can see in the Vue dev-tools that the proper data is stored in the items array. When I refresh, same happens. Only when I make changes in my code and save it, I see the proper data being displayed in the UI.

Code: WeeklyRanking.vue:

<script>
import { computed } from "vue"
import RankingItem from "./RankingItem.vue";
import {getUsersWithPoints} from "../../../data/data";

    export default {
    name: "WeeklyRanking",
    data() {
        return {
            items: [],
        };
    },
    created (){
        getUsersWithPoints().then((users) => {
            this.items = users;
            console.log(users);
        });
    },
    computed: {
        sortedRankings() {
            return [...this.items].sort((a, b) => b.points - a.points);
        },
        maxPoints() {
            return 95;
        }
    },
    components: { RankingItem },
}  
</script>

<template>
    <div class="weekly-ranking-container">
            <h1>Wochen - Ranking</h1>
            <div class="ranking-chart">
                <p v-for="r in items"> {{r.username}} </p>
                {{items}}
            </div>
            <button>
                <router-link to="/taskDone">+ Aufgabe erledigt</router-link>
            </button>
    </div>
</template>

<style>
    .weekly-ranking-container{
        padding: 24px;
        max-width: 400px;
        min-width: 300px;
    }
    .ranking-chart{
        display: flex;
        flex-direction: column;
        align-items: flex-start;
    }
</style>

The console.log shows the following: Console log result in created()

The Vue inspector show the data as follows: Vue inspector Data

What I have tried so far

  • Use fake data directly in the data() part - works. The UI shows the initial data that I mock here.

  • get fake data from data source with fake delay - works as well. Shows the proper data after the artificial delay. The interesting thing in this case, is that the Array in the console log looks different: Console log when using fake data

I could not make sense of the difference between those two logs, especially because the Vue inspector displays it exactly the same. Also, the part with the code saving does not make sense to me.

Below I show the data call code. I use firebase with the modular api. Thanks for your help!

Data call code:

async function getUsersWithPoints(){
    // With the commented part it works
    // const fakeUsers = [
    //     {username:"Marcelo", points:95, id:"PRirMre5IUeHP7BA08wh"},
    //     {username:"Sebse", points:80, id:"PRirMasdoiHP7BA08wh"},
    //     {username:"Simi", points:20, id:"yhaHRRxN7PFmfyHZqZS1"},
    // ];
    // await new Promise(r => setTimeout(r, 2000));
    // console.log("FAKE USERS:");
    // console.log(fakeUsers);
    // return fakeUsers;

    //with the below part it does not
    let users = [];
    const usersQuery = query(collection(db, `groups/${wgKey}/users`));
    const usersSnap = await getDocs(usersQuery);
    usersSnap.forEach(async(user)=>{
        const tasksQuery = query(collection(db, `${user.ref.path}/tasks`));
        const tasks = await getDocs(tasksQuery);
        let points = 0;
        tasks.forEach((task)=>{
            points+=task.data().points;
        });
        users.push({
            username: user.data().name,
            points: points,
            id: user.id
        });
    });
    return users;
}
```
Marcel Mayr
  • 111
  • 5
  • Could you try using _await_ instead of _.then()_? In the first approach, the program is waiting until the promise is resolved (or rejected) and in the second it's just going forward and when the promise is resolved it executes code inside _then()_ callback function. [Source](https://stackoverflow.com/a/68987830/15095104) – TymoteuszLao Jul 07 '22 at 12:30
  • @TymoteuszLao I tried it but that does not seem to be the issue. The promise itself is resolved because I log the result afterwards and the result contains the correct data as an array. It seems to be a difference in the data structure though, as the console log differs when I use the fake data (in which case it works). – Marcel Mayr Jul 07 '22 at 15:23

3 Answers3

1

I can remember having an identical issue with an Vue 2 project some years ago. As far as I can see Valentin Rapp gave you the correct explanation already. The documentation also states:

Array Mutators

Vue is able to detect when an reactive array's mutation methods are called

Source: https://vuejs.org/guide/essentials/list.html#array-change-detection

One possible solution would be, to push your resulting elements to the reactive array, therefore triggering it's mutators.

    // ...
    created (){
        getUsersWithPoints().then((users) => {
            users.forEach(user => {
                this.items.push(user)
            })
            console.log('Users:', this.items)
        })
    },
    // ...

Object Mutators

Depending on the size of your users-array (> 10k) this approach could potentially lag/very slow on some devices. Another approach would be, to use the reactive mutators of an object by updating an objects prop. Please note, that I haven't used that with Vue 3 options API yet, so this could be faulty:

    // ...
    data() {
        return {
            user: {
                items: [],
                loaded: false, // can be used for some loading animation
            },
        }
    },
    created (){
        getUsersWithPoints().then((users) => {
            this.user.items = users
            this.user.loaded = true
            console.log('Users:', this.user.items)
        })
    },
    // ...

Composition API

As stated before and propably lead to the confusion of Valentin Rapp is the useage of the options API in Vue 3. While this should be fully supported by Vue, with version 3 the composition API was implemented that could, besides other things, fix this problem for you:

    // Vue 3 Component with Composition API — not a 1:1 approach!
    // ...
    setup() {
        const items = ref([])

        getUsersWithPoints().then((users) => {
            users.forEach(user => {
                this.items.push(user)
            })
        })

        return {
            items,
        }
    },
    // ...

VS Code Saving Bug

This "bug" is more likely a confirmation for the problem stated above: mutators. Yet your given description is way to little to give you some detailed information here.

We don't know what your whole toolchain between "storing in vscode" to "bringing the new code to the browser" looks like. How does your code get transpiled? How is the file beeing watched? How is the hot reloading implemented?

Yet in general I think this will work like so:

  1. File gets stored within your editor
  2. Your toolchains watcher detects a changed file hash for one js file
  3. Your toolchain transpiles your code
  4. Your hot reload service will reload and inject the new code
  5. In order to sync your current data within your browser instance and the default data within the hot-reloaded component will be replaced correctly with usage of vues mutators.
  6. Your component therefor will detect the changes correctly. Your v-for get's rerendered and the data will be displayed correctly

But as I said, this is pure speculation and is purly depending on your setup.

  • Thank you for the detailed explanation!! That made a lot of things clear to me. The issue seemed to be in the data call though, I will explain in a seperate answer how I managed to fix it. – Marcel Mayr Jul 07 '22 at 18:38
  • Replacing an array (by assignment) works perfectly fine with any API. Test it out. Your assumption is completely wrong. Most likely, the imported function is returning the result before populating it. – tao Jul 07 '22 at 19:33
0

So I tried multiple things to fix it:

  • Switching to Composition API
  • Using Array Mutators instead of replacing the Array
  • Using async - await instead of then(..)

Unfortunately, none of that fixed the problem. So I looked more deeply into the getUsersWithPoints function to find what I think was the mistake: Using forEach with an async function. Using Promise.all instead fixed the issue. Here is the code:

async function getUsersWithPoints(){
    let usersExtended = new Array();
    const usersQuery = query(collection(db, `groups/${wgKey}/users`));
    const usersSnap = await getDocs(usersQuery);

    await Promise.all(usersSnap.docs.map(async(user) => {
        const tasksQuery = query(collection(db, `${user.ref.path}/tasks`));
        const tasks = await getDocs(tasksQuery);
        let points = 0;
        tasks.forEach((task)=>{
            points+=task.data().points;
        });
        usersExtended.push({
            username: user.data().name,
            points: points,
            id: user.id
        });
    }));


    return usersExtended;
}

Using array mutators seemed like a logical thing to do, although the docs say:

When working with non-mutating methods, we should replace the old array with the new one ... You might think this will cause Vue to throw away the existing DOM and re-render the entire list - luckily, that is not the case. Vue implements some smart heuristics to maximize DOM element reuse, so replacing an array with another array containing overlapping objects is a very efficient operation.

source: https://vuejs.org/guide/essentials/list.html#array-change-detection

So as far as I understand, replacing the array will not re-render the entire DOM, but vue uses "smart heuristics" to render the replacing array very efficiently. So now that I fixed the data call I tried replacing and using Array Mutators and both worked.

Marcel Mayr
  • 111
  • 5
-1

In your created lifecycle hook you overwrite the reactive data array. This will not trigger an update as described here:

https://vuejs.org/guide/essentials/list.html#array-change-detection

You will need to push the data to the array. To modify and clear it use splice.

In Vue 3 you will not have to deal with this anymore.

Valentin Rapp
  • 432
  • 6
  • 11
  • Thank you! I use Vue 3, so that does not seem to be the issue. I still gave it a try and it behaved the same way. I think there is an issue with my data structure, because the way it is logged in the console differs from the way the fake data arrays are logged. – Marcel Mayr Jul 07 '22 at 15:21