Nuxt3: how two chain two fetches? - vue.js

I am trying to chain two fetch in Nuxt3, with the second one calling an URL based on the result of the first one and the resulting "variable" to be used in the Vue component.
I'm trying with
<script setup>
const url = "myurl";
const { data } = await useFetch(url);
watch(data, async () => {
// do something with the result
url2 = data.value.result
const variable = await useFetch(url2);
});
</script>
but it looks like the block inside the watch is not able to modify the values of the variables at all (e.g. if I define it outside and try to update it inside, even with hard coded values)
Am I missing something very obvious here?

Something like this works perfectly fine, since all the fetching will be awaited
<script setup>
const { data } = await useFetch('https://jsonplaceholder.typicode.com/todos/1')
console.log('data', data.value.userId)
const { data: photos } = await useFetch(`https://jsonplaceholder.typicode.com/photos/${data.value.userId}`)
console.log('data2', photos.value)
</script>
<template>
<div>
first data: {{ data }}
</div>
<hr />
<div>
photos: {{ photos }}
</div>
</template>

Related

Vue 3 Fetching the data and passing to child components

I am building a page with several components in it, the data for all these components I need to get by making an ajax call.
As the child components are being mounted before the data comes in I'm getting undefined errors. Whats the best way to pass the data?
Here is a simplified version of what I'm trying to achieve
Stackblitz
In that example I have one Parent.vue and inside that we have 3 child coomponents, ID, Title, Body. After getting the data from API, the child componets are not updating.
Also for making the api calls I am directly calling load method inside setup() is there any better way of doing it?
Code Snippets from the stackblitz link
<template>
<h1>Data from Parent</h1>
{{ post.id }} - {{ post.title }} - {{ post.body }}
<h1>Data from Child</h1>
<IdChild :idP="post.id" />
<TitleChild :titleP="post.title" />
<BodyChild :bodyP="post.body" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
const post = ref()
const load = async () => {
let data = await fetch('https://jsonplaceholder.typicode.com/posts/1')
post.value = await data.json()
}
load()
</script>
When dealing with asynchronous data required in your components, you have basically two options:
You render the components before you get the data. It can be with default values or a loading state
<template>
<div v-if="!myAsyncData">Loading...</div>
<div v-else>{{ myAsyncData }}</div>
</template>
<script setup>
const myAsyncData = ref(null)
async function load() {
myAsyncData.value = await /* http call */
}
load() // will start to load and rendering the component
</script>
You await the response in the setup hook combined with the <Suspense> component so it starts to render only when the data is available.
Example (playground):
<!-- App.vue -->
<template>
<Suspense>
<Parent />
</Suspense>
</template>
<!-- Parent.vue -->
<template>
<div>{{ myAsyncData }}</div>
</template>
<script setup>
const myAsyncData = ref(null)
async function load() {
myAsyncData.value = await /* http call */
}
await load() // will wait this before rendering the component. Only works if the component is embebbed within a [`<Suspense>`](https://vuejs.org/guide/built-ins/suspense.html) component.
</script>

Input checbox v-model not checking with async data

I have a component that at the start of it, fetchs data from database. And it supose to check the input if the value is true or false. But the problem is that is not checking when its true. I tried :checked and it works, but i need the v-model because of the two way binding
code:
<input type="checkbox" v-model="data">
const data = ref(false)
onBeforeMount(async () => {
await data = fetchingData()
})
I didnt write the hole code, because the code its on another computer, this was from head. But i am having poblems with v-model not checking. If i use :checked it works like a charm
Maybe this is a rookie mistake, but without using :checked with :change i am not seing any solution.
You should use data.value instead of data for the reactivity. For the demo purpose I am directly assign the value as true. You can replace that with the API call code.
Live Demo :
<script type="module" src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0-rc.5/vue.esm-browser.js"></script>
<div id="app">
<input type="checkbox" v-model="data"/>
</div>
<script type="module">
import {ref, createApp, onBeforeMount } from 'https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0-rc.5/vue.esm-browser.js';
const app = createApp({
setup() {
let data = ref(false)
onBeforeMount(() => {
// data = true ❌
data.value = true // ✅
})
return { data };
}
});
app.mount('#app')
</script>
I do not think it is the checkbox, but that you should use the .value when you write to a ref. Otherwise you loose the reactivity and v-model will not work.
onBeforeMount(async () => {
data.value = await fetchingData()
})
Hope this helps.

v-for list diseapears after a second

I have a function which gives me list of objects. After update it renders correctly for a second than the list disappears. Any idea why this is happening ?
<script setup lang="ts">
import { getList, Version } from "#/services/firebaseService";
import { ref, watch } from "vue";
import { useRouter } from "vue-router";
const platformRef = ref(
useRouter().currentRoute.value.query.platform?.toString()
);
const abiRef = ref(useRouter().currentRoute.value.query.abi?.toString());
const list = ref([] as Version[]);
watch([platformRef, abiRef], async ([platform, abi]) => {
if (platform) {
list.value = await getList(platform, abi);
console.log(list.value);
}
});
</script>
<template>
<section>
<div class="form-selector">
<div class="form-block">
<o-radio v-model="platformRef" name="platform" native-value="android"
>Android</o-radio
>
<!-- rest of the form to set values for platform and abi -->
</div>
</div>
<div class="card" v-for="item in list" :key="item.name">
{{ item.name }}
</div>
</section>
</template>
and console output looks correctly (shows proxy to the array)
Proxy {}
Object[[Target]]:
Array(1)0:
{name: 'v0.2.0-beta.apk', link: 'https://firebasestorage.googleapis.com/v0/b/...'}
[[Prototype]]: Objectlength: 1
[[Prototype]]: Array(0)
UPDATE when chenged the function inside watch to this:
const result = await getList(platform, abi);
console.log(result);
console.log(result.length)
I get correct array but length 0...
I think that problem is in this string:
list.value = await getList(platform, abi);
You try to set the value field of list, but arrays do not have this field;
Instead, you should use this:
list = await getList(platform, abi);

How to use vue 3 suspense component with a composable correctly?

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?
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.

Making API call using Axios with the value from input, Vue.js 3

I am making an app using this API. The point I'm stuck with is calling the API. If I give the name of the country, the data of that country comes.
Like, res.data.Turkey.All
I want to get the value with input and bring the data of the country whose name is entered.
I am getting value with searchedCountry. But I can't use this value. My API call does not happen with the value I get. I'm getting Undefined feedback from Console.
Is there a way to make a call with the data received from the input?
<template>
<div>
<input
type="search"
v-model="searchedCountry"
placeholder="Search country"
/>
</div>
</template>
<script>
import axios from 'axios';
import { ref, onMounted} from 'vue';
export default {
setup() {
let data = ref([]);
const search = ref();
let searchedCountry = ref('');
onMounted(() => {
axios.get('https://covid-api.mmediagroup.fr/v1/cases').then((res) => {
data.value = res.data.Turkey.All;
});
});
return {
data,
search,
searchedCountry,
};
},
};
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
I'm work with Vue.js 3
There are a few things wrong with your code:
Your axios call is only called once, when the component mounts (side note here, if you really want to do something like that, you can do it directly within the setup method)
You don't pass the value from searchedCountry to the axios API
Use const for refs
I'd use a watch on the searchedCountry; something like this (I don't know the API contract):
<template>
<div>
<input
type="search"
v-model="searchedCountry"
placeholder="Search country"
/>
</div>
</template>
<script>
import axios from 'axios';
import { ref, watch } from 'vue';
export default {
setup() {
const searchedCountry = ref('');
const data = ref([]);
watch(
() => searchedCountry,
(country) => axios.get(`https://covid-api.mmediagroup.fr/v1/cases/${country}`).then((res) => data.value = res.data.Turkey.All);
);
return {
data,
searchedCountry,
};
},
};
</script>