2

I am using Vue-3 and Vite in my project. in one of my view pages called Articles.vue I used suspense component to show loading message until the data was prepared. Here is the code of Articles.vue:

<template>
<div class="container">
    <div class="row">
        <div>
            Articles menu here
        </div>
    </div>
    <!-- showing articles preview -->
    <div id="parentCard" class="row">
        <div v-if="error">
            {{ error }}
        </div>
        <div v-else>
            <suspense>
                <template #default>
                    <section v-for="item in articleArr" :key="item.id" class="col-md-4">
                        <ArticlePrev :articleInfo = "item"></ArticlePrev>
                    </section>
                </template>
                <template #fallback>
                    <div>Loading...</div>
                </template>
                
            </suspense>
        </div>
    </div> <!-- end of .row div -->
</div>
</template>

<script>
import DataRelated from '../composables/DataRelated.js'
import ArticlePrev from "../components/ArticlePrev.vue";
import { onErrorCaptured, ref } from "vue";
/* start export part */
export default {
    components: {
        ArticlePrev
    },
    
    setup (props) {
        const error = ref(null);
        onErrorCaptured(e => {
            error.value = e
        });

        const {
            articleArr
        } = DataRelated("src/assets/jsonData/articlesInfo.json");
        
        return {
            articleArr,
            error
        }
    }

} // end of export
</script>

<style scoped src="../assets/css/viewStyles/article.css"></style>

As you could see I used a composable js file called DataRelated.js in my page that is responsible for getting data (here from a json file). This is the code of that composable:

/* this is a javascript file that we could use in any vue component with the help of vue composition API */
import { ref } from 'vue'

export default function wholeFunc(urlData) {
  const articleArr = ref([]);
  const address = urlData;

  const getData = async (address) => { 
    const resp = await fetch(frontHost + address);
    const data = await resp.json();
    articleArr.value = data;
  }

  setTimeout(() => {
    getData(address);
  }, 2000);
 

  return {  
    articleArr
  }
} // end of export default

Because I am working on local-host, I used JavaScript setTimeout() method to delay the request to see that the loading message is shown or not. But unfortunately I think that the suspense component does not understand the logic of my code, because the data is shown after 2000ms and no message is shown until that time. Could anyone please help me that what is wrong in my code that does not work with suspense component?

hamid-davodi
  • 1,602
  • 2
  • 12
  • 26

1 Answers1

1

It's a good practice to expose a promise so it could be chained. It's essential here, otherwise you'd need to re-create a promise by watching on articleArr state.

Don't use setTimeout outside the promise, if you need to make it longer, delay the promise itself.

It could be:

  const getData = async (address) => { 
    await new Promise(resolve => setTimeout(resolve, 2000);

    const resp = await fetch(frontHost + address);
    const data = await resp.json();
    articleArr.value = data;
  }
  const promise = getData(urlData)

  return {  
    articleArr,
    promise
  }

Then:

async setup (props) {
    ...
    const { articleArr, promise } = DataRelated(...);
    await promise 
    ...

If DataRelated is supposed to be used exclusively with suspense like that, it won't benefit from being a composable, a more straightforward way would be is to expose getData instead and make it return a promise of the result.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • I tried to implement your method. But I usually get confused with **asynchronous JavaScript**. I put the edited code in my question but that does not work. it does not give me any error and the data is not loaded on the page. Also the ```setTimeout()``` does not work. could you please give me a more detailed code especially with using ```setTimeout()``` in your answer. – hamid-davodi Dec 25 '21 at 13:08
  • I posted only the parts that differ, you shouldn't have change getData, it's messy now and contains several kinds of antipatterns. "articleArr.value = promise1" - there's no this line in the answer, it shouldn't exist. `promise` object is a promise of a value, not a value itself, it's incorrect to assign articleArr to it. You should do this when a value is available, inside getData. If you have problems with general JS questions, consider solving them first before digging deeper into Vue. As for timeout, see https://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep – Estus Flask Dec 25 '21 at 14:54
  • thanks for your help about ```setTimeout()```, I just rename ```promise``` to ```promise1``` to not confuse with **Promise** of js. now it stops 2000ms and if I ```console.log(articleArr.value);``` after ```await promise;``` in my ```setup()``` it has the array of data, but I don't know why **vue** does not recognize it as a **ref** data? the articles data does not load in the web page and no message (error or loading). – hamid-davodi Dec 25 '21 at 15:24
  • Lowercase `promise` is an instance of `Promise` class, it's totally acceptable to use it as a name. "but I don't know why vue does not recognize it" - what do you mean exactly? I'm not sure how your components are structured, but there should be child component that has async setup and uses a composable (no suspense), and there should be parent component that uses suspense and child component (no async setup, no composable). – Estus Flask Dec 25 '21 at 16:11
  • I had put the whole code of my ```Articles.vue``` page in my question. When I put the exact codes that you said in my composable **js** file and my ```Articles.vue``` file, the browser page **does not** show any content and also there is **no error** in my development environment or my console. I'm not sure, but from [this link](https://v3.vuejs.org/guide/migration/suspense.html#introduction) I also noticed that vue also said that **Be very careful about using `await` inside `setup`**. – hamid-davodi Dec 25 '21 at 19:17
  • You need to structure components exactly as I explained above. You won't be able to make it work with only Articles.vue, like you originally did. That you have no content and no errors means that suspense was messed up. If you want to do this in a single component, you don't need suspense at all because you could just conditionally output "Loading" message with v-if. Suspense is needed only when you have nested asynchronous components, and a parent can't determine when their initialization completes. – Estus Flask Dec 25 '21 at 21:12