8

I've installed Vue 3 and want to populate a reactive object with fetch from a JSON API.

My component looks like below. I don't get any errors but I don't get any results either.

<template>
  <div>
    {{ state.test.total }}
  </div>
</template>

<script>
import { reactive } from "vue";

export default {
  setup() {
    const state = reactive({
      test: null,
    });

    state.test = async () => {
      return await fetch("https://api.npms.io/v2/search?q=vue");
    };

    return {
      state,
    };
  },
};
</script>

I expected to get a number on the screen because that's what's in total in the JSON file.

On state.test

If I only output state.test I get the output below.

function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }

Any idea how I can get around it?

3 Answers 3

8

When you do:

a = async function test() {
}

You're assigning a function to your state. But if you you do

a = (async function test() {
})();

You're still assigning a promise to a, rather than a value. If you want to assign a value you need to resolve this promise:

funcResult = await a;

Your setup function is not a proper place for code that should be executed during lifetime of your component. You can think of setup function as a factory for your component. Thus setup function should always be synchronous (no async keywrord), otherwise vue would not know what it should display while setup is resolving. Luckily you can use hooks in composition API:

import { onMounted, reactive } from "vue";

export default {
  setup() {
    const state = reactive({
      test: null,
    });


    onMounted(async () => {
      const response = await fetch("https://api.npms.io/v2/search?q=vue");
      state.test = await response.json();
    });


    return {
      state,
    };
  },
};

EDIT

Taking into account @boussadjra-brahim answer, you can actually define async setup function, but only in case if you wrap your component with <Suspense>. So you can either go with this or that variant.

Sign up to request clarification or add additional context in comments.

7 Comments

That worked after some modifications. You have switched )}; at the end of onMounted. I forgot to add .then((response) => response.json()); at the end of my fetch. Thanks so much!
+1 "You can think of setup function as a factory for your component. Thus setup function should always be synchronous", useful advice.
@matt What do you guys think of the async setup() solution below? Is it good or is it a bad practice?
@JensTörnell It's unnecessary for this case. The setup function doesn't need to wait. You can do something that returns a promise and use .then() to populate a reactive variable. For anything more advanced like loading indicators, you can use the Suspense component.
@JensTörnell I've posted an answer.
|
4

I think it's better not to wait.

Example:

<template>
    <div>
        {{ state }}
    </div>
</template>


<script>
import { ref } from 'vue';

export default {
    setup() {
        const state = ref({});

        fetch('https://api.npms.io/v2/search?q=vue')
            .then(response => response.json())
            .then(data => state.value = data);

        return { state };
    },
};
</script>

3 Comments

This use case accepts the three proposed solutions, for example my solution could be useful if the component is a data display like tables or lists when we could add a loading indicator then show the data, but it will be bad if the component contains another dependent logic
@BoussadjraBrahim Yes, I imagine Suspense being useful for that. I don't have enough experience with it to know whether the composition functions should be async or not.
thanks for the link, they should be async and responsible on an unique logic for fetching and showing data in a grid, let's take an example of a data table which could contain a search input, a grid and pagination component, the suspense should only wrap the part that could be loaded async which is the grid.
1

You should add async to setup option and await to the fetch function and wrap your child component by Suspense component in the parent component :

<template>
    <div>
      {{ state.test.total }}
    </div>
</template>

<script>
import { reactive } from "vue";
export default {
  async setup() {
    const state = reactive({
      test: null,
    });

    try{
      state.test =  await fetch("https://api.npms.io/v2/search?q=vue");
     } catch (err) {
       console.error(err)
   }

    return {
      state,
    };
  },
};

in parent :

<template>
  <Suspense>
    <Child/>
  </Suspense>
</template>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.