AsyncData with multiple requests in Nuxt - vue.js

I'm trying to get the answer from two API routes and depending on the result display the data. But for some reason, when I trying to use more than 1 axios call it doesn't work, failing with 404/500 error.
I've tried following:
<template>
<div v-if="blogPost">
<p>post</p>
</div>
<div v-else-if="blogCategoryPosts">
<p>category,posts</p>
</div>
</template>
<script>
export default {
async asyncData({ $axios, app, route }) {
const blogPost = await $axios.get(`${process.env.API_DOMAIN}/api/blog/posts${route.path}`)
const blogCategoryPosts = await $axios.get(`${process.env.API_DOMAIN}/api/blog/categories${route.path}`)
return {
blogPost: blogPost.data,
blogCategoryPosts: blogCategoryPosts.data,
}
},
}
</script>
and
<script>
export default {
async asyncData({ $axios, app, route}) {
const [blogPost, blogCategoryPosts] = await Promise.all([
$axios.get(`${process.env.API_DOMAIN}/api/blog/posts${route.path}`),
$axios.get(`${process.env.API_DOMAIN}/api/blog/categories${route.path}`),
])
return {
blogPost: blogPost.data,
blogCategoryPosts: blogCategoryPosts.data,
}
},
}
</script>
Each call works fine separately but together they don't. Any idea why is that?

You should await your Promise.all like this
const [blogPost, blogCategoryPosts] = await Promise.all([
$axios.get(`${process.env.API_DOMAIN}/api/blog/posts${route.path}`),
$axios.get(`${process.env.API_DOMAIN}/api/blog/categories${route.path}`),
])
Also, don't forget the , at the end of the first $axios.
I gave a similar answer here few time ago.
PS: if you want to have those issues fixed quickly, use ESlint.
If you want a tutorial on how to have both ESlint and Prettier, you can follow my tutorial here.

So in my case it was sufficient to point on .then and .catch for axios
export default {
async asyncData({ $axios, app, route}) {
const blogPost = await $axios.get(`${process.env.API_DOMAIN}/api/blog/posts${route.path}`).then(response => response.data).catch(error => {})
const blogCategoryPosts = await $axios.get(`${process.env.API_DOMAIN}/api/blog/categories${route.path}`).then(response => response.data).catch(error => {})
return {
blogPost: blogPost,
blogCategoryPosts: blogCategoryPosts,
}
},
}
Everything worked well. Also I sort of misunderstood 500 error, i thought it a generic message, but my API was just telling me that category not found.

Related

How to access a injected repository from a component's method

Lets say we injected this repository on a plugin/service-container.js
import nodeFetch from 'node-fetch'
import { AbortController as NodeAbortController } from 'node-abort-controller'
import HttpClient from '#/services/httpClient'
import PostRepository from '#/repositories/posts'
export default ({ app }, inject) => {
if (!process.client || app.context.env.NUXTJS_DEPLOY_TARGET === 'server') {
inject('postRepository', postRepository)
}
}
I have always acceded to API repositories from the asyncData method, like so:
export default {
async asyncData ({ $postRepository, }) {
const posts = await $postRepository.getAllPaginated(page, 11)
return {
posts,
}
}
}
But I need to access to it in a method, this is actually working but:
I doesn't look the right way because i'm caching in the component's data()
It fires this lint error:
Async method 'asyncData' has no 'await' expression.eslintrequire-await
What's the right way? I Can't find it online (the only examples I found involved using the Store)
export default {
async asyncData ({ $postRepository }) {
this.$postRepository = $postRepository
},
methods: {
async loadMore () {
if (this.page < this.posts.numPages) {
const posts = await this.$postRepository.getAllPaginated(this.page + 1, 11)
}
}
}
}
The error is coming from here
async asyncData ({ $postRepository }) {
this.$postRepository = [missing await here] $postRepository
},
From the documentation
This hook can only be used for page-level components. Unlike fetch, asyncData cannot access the component instance (this). Instead, it receives the context as its argument. You can use it to fetch some data and Nuxt will automatically shallow merge the returned object with the component data.
Hence, you cannot use any kind of this.loadMore in asyncData because it doesn't have access to the instance yet. So, inject is indeed the proper way of doing things.
With a plugin like that
export default ({ _ }, inject) => {
inject('customTest', async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
return await response.json()
})
}
And a page like this
<template>
<div>
<pre>item: {{ item }}</pre>
</div>
</template>
<script>
export default {
async asyncData({ $customTest }) {
const item = await $customTest()
return { item }
},
}
</script>
It is not calling a method but you could totally use this.$nuxt.refresh() to fetch it again and increment the index of the repository call after an update in the store.
Which could be referenced like
await fetch(`https://jsonplaceholder.typicode.com/todos/${indexFromVuex}`)
You could of course keep it local too
<template>
<div>
<pre>item: {{ item }}</pre>
<button #click="fetchNewItem">fetch new item</button>
</div>
</template>
<script>
export default {
async asyncData({ $customTest }) {
const item = await $customTest()
return { item }
},
data() {
return {
index: 1,
}
},
methods: {
async fetchNewItem() {
this.index += 1
this.item = await this.$customTest(this.index)
},
},
}
</script>
So yeah, I don't think that there are other possible approaches with asyncData.
The fetch() hook is a bit more flexible but it's also totally different too regarding how it is working.
Anyway, with those 2 approaches you could totally have enough to solve the issue of your HTTP call.
It seems that an injected dependency can be accessed (in this case) with simply this.$postRepository inside any method so I didn't even need that asyncData

useFetch in onMounted not fetching data on direct link open nuxt3

I'm using the useFetch to fetch the data in the composition api and then calling the function in onMounted hook in components, here is the code.
useShows.ts (composable)
export function useShows(){
var shows = useState<Show[]>('shows')
const fetchShows = async() => {
const {data, pending} = await useFetch<Show[]>('http://localhost:3000/shows')
shows.value = data.value
}
return {shows, fetchShows}
}
shows.vue
<script setup lang="ts">
var { shows, fetchShows } = useShows()
onMounted(() => {
console.log("On mounted called")
fetchShows()
})
</script>
<template>
<div>{{shows}}</div>
</template>
When I'm navigating to /shows from the home page it is working fine, but when I direct open the link localhost/shows it is not working and only giving me the null.
I have the exact same problem. I solved it by wrapping it within a nextTick.
await nextTick(async () => {
await fetchShows()
})

asyncData() or fetch() does not work async, but in mounted it works correctly

I'm enough. I spent a few hours, but cant figure out how it works. My code works in SPA by the mounted() hook, but I migrate to nuxt now and faced with problems.
I'm trying to get data from store by asyncData()
async asyncData({store}) {
console.log("start getting schools")
const schools = await store.dispatch('GET_SCHOOLS_FROM_API').then((schools) => {
console.log(schools)
})
console.log(schools + " schools")
return schools
},
or fetch()
async fetch({store}) {
const sch = await store.dispatch('GET_SCHOOLS_FROM_API')
this.schools = store.SCHOOLS; //SCHOOLS is a getter in store
});
},
but it does not wait for dispatch to complete and returns undefind.
store:
async GET_SCHOOLS_FROM_API({ commit }) {
return axios.get('http://localhost:3000/schools').then((schools) => {
commit('SET_SCHOOLS_TO_STATE', schools.data)
return schools;
}).catch((error) => {
console.log(error);
return error;
})
}
does somebody have an idea where I am wrong?
The problem is that you are intersecting the Promise with an additional then block, when you add the await you are actually waiting for the result of the promise inserted in the then block, there you have a function that doesn't return anything, hence it returns undefined.
This can be easily fixed like:
async asyncData({ store }) {
console.log("start getting schools");
const schools = await store.dispatch('GET_SCHOOLS_FROM_API');
console.log(schools);
return { schools };
}
Another thing is that according to the example in the docs they return the value as an object, can you try that?
Ref https://nuxtjs.org/docs/2.x/features/data-fetching/
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.description }}</p>
</div>
</template>
<script>
export default {
async asyncData({ params, $http }) {
const post = await $http.$get(`https://api.nuxtjs.dev/posts/${params.id}`)
return { post } // <===== Like this
}
}
</script>
the thing was, that I did not remove data() from a component, and it was a mistake. After that, the code above works correctly

How To Ensure Vue Main Template Create Method Finishes Before Router-View Inner Template Create Method Starts

I have a simple setup with a main Vue template like this:
<template>
<div>
[Other code here...]
<v-app style="overflow-y:hidden">
<router-view class="view"></router-view>
</v-app>
</div>
</template>
<script>
export default {
methods: {
created() {
//I NEED THIS CODE TO FINISH FIRST BEFORE ROUTER-VIEW
//TEMPLATE CREATED METHOD STARTS
await XxxService.xXXxXX()
.then((response) => {
localStorage.mySpecialVariable = yyyyy;
})
.catch(() => {
});
}
}
}
</script>
Currently, there is a race condition where the value of localStorage.mySpecialVariable is null when the inner template runs its create() method. Sometimes it's there, sometimes it's not unless I run the page twice.
How can I ensure that the outer main template code completes before anything continues?
If the app depends on an API response, I would defer mounting the app until the response is received:
// main.js
ApiService.fetch()
.then((response) => {
localStorage.mySpecialVariable = response.data.foo;
})
.catch((error) => {
console.error(error);
})
.then(() => {
// ok to mount
new Vue({/*...*/}).$mount();
})
So after a ton of testing and research, I ended up updating the router.js file and running my code before each route is executed which did exactly what I need it to do.
router.sj
router.beforeEach((to, from, next) => {
doTheThing(to.query.Token, next);
});
let doTheThing = async (token, next) => {
await apiService.doTheThing()
.then((response) => {
//Do Stuff Here
next();
})
.catch(() => {
});
}
So what happens is that the function above will run and complete before any of the page specific code runs, which was the main goal and question I was asking. I hope someone else finds this helpful.

How to use twilio with dynamic routes in nuxt

Hoping I can explain this clearly and someone has some insight on how I can solve this.
I am trying to enter a input then have a text message delivered to the number that was entered. That simple.
On the homepage, I have an input component with:
<template>
<form class="right-card" #submit.prevent="submit">
<input v-model="search" />
<button class="clear" type="submit" v-on:click="submit"></button>
</form>
</template>
With this function set as a method to pass the param
export default {
data () {
return {
search: ''
}
},
methods: {
submit: function (event) {
this.$router.push(`sms/${this.search}`)
}
}
}
Then I have a /sms page located in pages/sms/_sms.vue which is landed on once the form is submitted
<template>
<div>
<h1>Success Page {{phoneNumber}} {{$route.params}}</h1>
<KeyboardCard/>
</div>
</template>
<script>
import KeyboardCard from '~/components/KeyboardCard.vue'
import axios from '~/plugins/axios'
export default {
asyncData ({ params, error }) {
return axios.get('/api/sms/' + params.sms)
.then((res) => {
console.log(res)
console.log(params)
return { phoneNumber: res.data }
})
.catch((e) => {
error({ statusCode: 404, message: 'Sms not found' })
})
},
components: {
KeyboardCard
}
}
</script>
And finally within api/sms/sms.js I have this on express running.
(note my API keys are replaced with placeholder)
router.get('/sms/:sms', (req, res, next) => {
console.log('express reached')
const accountSid = 'ACCOUNTSIDPLACEHOLDER'
const authToken = 'AUTHTOKENPLACEHOLDER'
const client = require('twilio')(accountSid, authToken)
client.messages.create({
to: '14169190118',
from: '+16477993562',
body: 'This is the ship that made the Kessel Run in 14 parsecs?!'
})
.then((message) => console.log(message.sid))
})
How can I pass the parameter.sms within the to field in my /api/routes/sms.js
Expected: When user enters # into the input how can the api/sms/:sms be called dynamically to the number that was typed in the input component?
Thanks in advance if anyone see's whats going on here :)
Edit: I have my middleware defined in the nuxt.config file, like so:
serverMiddleware: [
// API middleware
'~/api/index.js'
]
and my api/index.js file has:
const express = require('express')
// Create express instnace
const app = express()
// Require API route
const sms = require('./routes/sms')
// Import API Routes
app.use(sms)
// Export the server middleware
module.exports = {
path: '/api',
handler: app
}
I guess this is more an Express.js related question than a Vue.js question.
You can use the passed sms param from your request, like this:
router.get('/sms/:sms', (req, res, next) => {
console.log('express reached')
const accountSid = 'ACCOUNTSIDPLACEHOLDER'
const authToken = 'AUTHTOKENPLACEHOLDER'
const client = require('twilio')(accountSid, authToken)
client.messages.create({
to: req.params.sms,
from: '+16477993562',
body: 'This is the ship that made the Kessel Run in 14 parsecs?!'
})
.then((message) => console.log(message.sid))
})