How do I call the API with Axios on the (child) component and present the result on the Page (parent) component in Nuxt? - vue.js

If I run this code on the Page component (mountains.vue) it works and I get the data from the API with help with Axios:
<template>
<div>
<ul>
<li v-for="(mountain, index) in mountains" :key="index">
{{ mountain.title }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
mountains: [],
};
},
async asyncData({ $axios }) {
const mountains = await $axios.$get("https://api.nuxtjs.dev/mountains");
return { mountains };
},
};
</script>
But I want to put this code in a component (MountainsList) and do the Axios call in the component (MountainsList), but display the data on the Page component (mountains.vue) by injecting the component in Nuxt like this:
<template>
<MountainsList />
</template>
Now when I run the code, the data using Axios doesn't appear anymore... So how do I inject the data to the Page component above doing the Axios call in the child component?

asyncData only works on a page
From the docs
asyncData is called every time before loading the page component
One way you can accomplish what you want is passing the mountains in as a prop to the MountainList component. Something like below...
<template>
<MountainList :mountains="mountains" />
</template>
<script>
export default {
async asyncData({ $axios }) {
const mountains = await $axios.$get("https://api.nuxtjs.dev/mountains");
return { mountains };
},
};
</script>
And the component with the code and prop mountains...
<template>
<div>
<ul>
<li v-for="(mountain, index) of mountains" :key="index">
{{ mountain.title }}
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['mountains'],
};
</script>
If you really want to make the API call in the child component you can use the fetch method.
Also you should not define a data() property on the page. I believe it will overwrite the server rendered data.

According to official docs :
Components in this directory will not have access to asyncData.
It means that any components inside the components folder cannot access that method.

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>

Vue: Reuse loading spinner template and logic

I've multiple (20+) pages where I need the following code:
<template>
<template v-if="isLoading">
<Spinner full size="medium" />
</template>
<template v-else>
<p>Page data</p>
</template>
</template>
<script>
export default {
computed: {
isLoading() {
return this.$store.getters['loader/isLoading'];
},
},
};
</script>
I don't want to rewrite this part everytime I need it so is there a way to create something like a higher order component with access to the computed method and a way to add the hoc to the script tag of the different vue files? Or another way to archive this?
I could recommend extending the spinner component where you want to show the spinner. I've created a boilerplate setup that show a very simple implementation of this approach here.
The main idea is to expose a default slot for you spinner component, and wrap the page component in that slot.
<template>
<div>
<Spinner v-if="isLoading" full size="medium" />
<!-- Slot for component data -->
<slot v-else></slot>
</div>
</template>
<script>
export default {
computed: {
isLoading() {
return this.$store.getters['loader/isLoading'];
},
},
};
</script>
Then in any component that you want to show the spinner:
<template>
<spinner>
<!-- Pass here the component content to be shown after the spinner is hidden -->
</spinner>
</template>
<script>
import Spinner from "./spinner";
export default {
name: "Page1",
extends: Spinner,
components: { Spinner }
};
</script>

parent component is not getting data from child in Nuxt app

This is driving me crazy so I hope that anyone can help.
I made a Nuxt app with #nuxt/content and I'm using Netlify-CMS to create content. That all seems to work fine. However I'm trying to display a component that contains a loop of the MD-files that I have, but in the index.vue nothing of the loop is displayed.
I know (a little) about props and $emit, but as I am not triggering an event this dosen't seem to work.
Component code:
<template>
<section>
<h1>Releases</h1>
<li v-for="release of rfhreleases" :key="release.slug">
<h2>{{ release.artist }}</h2>
</li>
</section>
</template>
<script>
export default {
components: {},
async asyncData({ $content, params }) {
const rfhreleases = await $content('releases', params.slug)
.only(['artist'])
.sortBy('createdAt', 'asc')
.fetch()
return {
rfhreleases,
}
},
}
</script>
And index.vue code:
<template>
<div>
<Hero />
<Releases />
<About />
<Contact />
</div>
</template>
<script>
export default {
head() {
return {
script: [
{ src: 'https://identity.netlify.com/v1/netlify-identity-widget.js' },
],
}
},
}
</script>
If I place my component code as part of index.vue, everything work, but I would love to avoid that and thats why I'm trying to place the loop in a component.
As stated on the Nuxt documentation:
This hook can only be placed on page components.
That means asyncData only works on components under pages/ folder.
You have several options:
You use fetch instead. It's the other asynchronous hook but it's called from any component. It won't block the rendering as with asyncData so the component it will instanciated with empty data first.
You fetch your data from the page with asyncData and you pass the result as a prop to your component
<template>
<div>
<Hero />
<Releases :releases="rfhreleases" />
<About />
<Contact />
</div>
</template>
<script>
export default {
async asyncData({ $content, params }) {
const rfhreleases = await $content('releases', params.slug)
.only(['artist'])
.sortBy('createdAt', 'asc')
.fetch()
return {
rfhreleases,
}
},
}
</script>

asyncData in component of Nuxtjs isn't working

I have an issue with the nuxt.js project. I used async the component but when it rending, async wasn't working. This is my code.
I have view document in https://nuxtjs.org/api/ but I don't know what is exactly my issue
Test.vue (component)
<template>
<div>
{{ project }}
</div>
</template>
<script>
export default {
data() {
return {
project : 'aaaa'
}
},
asyncData() {
return {
project : 'bbbb'
}
}
}
</script>
This is index.vue (page)
<template>
<test></test>
</template>
<script>
import Test from '~/components/Test.vue'
export default {
components : {
Test
}
}
</script>
My expected result is
bbbb
But when running on http://localhost:3000 this is actual result
aaaa
I try to search google many times but don't have expected solution for me. Someone help me, please.
Thanks for helping.
The components/ folder must contains only "pure" Vue.js components
So you can't use asyncData inside.
Read this FAQ: https://nuxtjs.org/faq/async-data-components#async-data-in-components-
components/Test.vue (component)
<template>
<div>
{{ project }}
</div>
</template>
<script>
export default {
props: ['project'] // to receive data from page/index.vue
}
</script>
pages/index.vue (page)
<template>
<test :project="project"></test>
</template>
<script>
import Test from '~/components/Test.vue'
export default {
components : {
Test
},
asyncData() {
return {
project : 'bbbb'
}
}
}
</script>
You cannot use asyncData in component.
You can choose to use the fetch method instead.
<template>
<div>
<p v-if="$fetchState.pending">Loading....</p>
<p v-else-if="$fetchState.error">Error while fetching mountains</p>
<ul v-else>
<li v-for="(mountain, index) in mountains" :key="index">
{{ mountain.title }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
mountains: []
}
},
async fetch() {
this.mountains = await fetch(
'https://api.nuxtjs.dev/mountains'
).then(res => res.json())
}
}
</script>

Undefined props in Vue js child component. But only in its script

I have just started using Vue and experienced some unexpected behavior. On passing props from a parent to child component, I was able to access the prop in the child's template, but not the child's script. However, when I used the v-if directive in the parents template (master div), I was able to access the prop in both the child script and child template. I would be grateful for some explanation here, is there a better was of structuring this code? See below code. Thanks.
Parent Component:
<template>
<div v-if="message">
<p>
{{ message.body }}
</p>
<answers :message="message" ></answers>
</div>
</template>
<script>
import Answers from './Answers';
export default {
components: {
answers: Answers
},
data(){
return {
message:""
}
},
created() {
axios.get('/message/'+this.$route.params.id)
.then(response => this.message = response.data.message);
}
}
</script>
Child Component
<template>
<div class="">
<h1>{{ message.id }}</h1> // works in both cases
<ul>
<li v-for="answer in answers" :key="answer.id">
<span>{{ answer.body }}</span>
</li>
</ul>
</div>
</template>
<script>
export default{
props:['message'],
data(){
return {
answers:[]
}
},
created(){
axios.get('/answers/'+this.message.id) //only worls with v-if in parent template wrapper
.then(response => this.answers = response.data.answers);
}
}
</script>
this.message.id only works with v-if because sometimes message is not an object.
The call that you are making in your parent component that retrieves the message object is asynchronous. That means the call is not finished before your child component loads. So when your child component loads, message="". That is not an object with an id property. When message="" and you try to execute this.message.id you get an error because there is no id property of string.
You could continue to use v-if, which is probably best, or prevent the ajax call in your child component from executing when message is not an object while moving it to updated.