Nuxt-content not working with asyncData in a component - vue.js

I'm trying to fetch content from my content/blog folder but I can't make it work in the components/menu.vue page since it's a component ?
This is working in the page/index.vue but not in components/menu.vue. Why?
I'm new to all this and maybe I'm doing something wrong here, maybe I'm not able to fetch content from a component... I don't know.
<template>
<div class="container">
<div class="articles">
<div class="article" v-for="article of articles" :key="article">
<nuxt-link :to="{ name: 'slug', params: { slug: article.slug } }">
<div class="article-inner">
<img :src="require(`~/assets/${article.img}`)" alt="" />
<div class="detail">
<h3>{{ article.title }}</h3>
<p>{{ article.description }}</p>
</div>
</div>
</nuxt-link>
</div>
</div>
</div>
</template>
<script>
export default {
async asyncData ({ $content, params }) {
const articles = await $content('blog', params.slug)
.only(['title', 'description', 'img', 'slug'])
.sortBy('createdAt', 'asc')
.fetch()
console.log('bongo')
return {
articles
}
},
data () {
return {
num: this.$route.name
}
},
computed: {
currentRouteName () {
return this.$route.name
}
}
}
</script>

As written here: https://nuxtjs.org/docs/features/data-fetching#async-data
asyncData is only available for pages and you don't have access to this inside the hook.
So yes you can use asyncData only in a page.
An alternative would be to use a non-blocking fetch() hook as shown here: https://nuxtjs.org/docs/features/data-fetching#accessing-the-fetch-state

Related

Displaying posts details using Json returns undefine in vue3

After successfully fetching posts using JSON, I also want to display the details of the post on the post details.vue page but I get an undefined error.
I am sure there is something I am not doing right but I can't figure it out as well. Please someone help me with an idea on how to figure it out. Thanks.
Here is the code to fetch the posts from JSON
<div class="w-full lg:w-3/4 mb-7 p-1">
<div class="max-w-md bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl mb-6" v-for="post in posts" :key="post.id">
<div class="flex">
<div class="shrink-0">
<img class="h-28 w-full object-cover md:h-full md:w-28" src="../assets/img.png" alt="Man looking at item at a store">
</div>
<div class="pl-4 pt-2">
<div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">{{post.cat}}</div>
<router-link :to="{name: 'PostDetails', params:{id: post.id} }">
<h1 class="block mt-1 text-lg leading-tight font-medium text-black hover:underline font-bold">{{post.title}}</h1>
</router-link>
<!-- <p class="mt-2 text-slate-500">Getting a new business off the ground is a lot of hard work.</p> -->
</div>
</div>
</div>
</div>
JS
<script>
export defualt {
data(){
return{
posts:[]
}
},
mounted(){
fetch('http://localhost:3000/posts')
.then(res => res.json())
.then(data => this.posts = data)
.catch(err => console.log(error.message))
}
}
</script>
Code to display posts details
<template>
<h1>Current Post You Are reading</h1>
<div v-if="post">
<p>The Post Id is {{id}}</p>
<h1>{{post.title}}</h1>
</div>
Js
<script>
export default {
props:['id'],
data(){
return{
post: null
}
},
mounted(){
fetch('http://localhost:3000/posts/' + this.id)
.then(res => res.json())
.then(data => this.post = data)
.catch(err => console.log(error.message))
}
}
The Outcome
I think my sample will help you,
PostList.vue
<template>
<div>
<div v-for="item in posts" :key="item.id">
<router-link :to="'/postdetails/'+item.id">{{item.first_name}}</router-link>
<h1>{{item.first_name}}</h1>
<h2>{{item.last_name}}</h2>
<div>{{item.email}}</div>
<div>
<img v-bind:src="item.avatar" alt="">
</div>
</div>
</div>
</template>
<script>
export default {
name: 'PostList',
data() {
return {
posts: []
}
},
created() {
fetch('https://reqres.in/api/users')
.then(res => res.json())
.then(data => {
this.posts = data.data
console.log("this.posts", this.posts)
})
.catch(err => console.log(err))
}
}
</script>
PostDetails.vue
<template>
<div>
<div v-for="item in posts" :key="item.id">
<h1>{{ item.first_name }}</h1>
<h2>{{ item.last_name }}</h2>
<div>{{ item.email }}</div>
<div>
<img v-bind:src="item.avatar" alt="" />
</div>
</div>
</div>
</template>
<script>
export default {
name: "PostDetails",
data() {
return {
msg: "welcome to PostList page",
posts: [],
id: this.$route.params && this.$route.params.id,
};
},
created() {
fetch("https://reqres.in/api/users/" + this.id)
.then((res) => res.json())
.then((data) => {
this.posts = data;
})
.catch((err) => console.log(err));
},
};
</script>
And in router index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '#/components/HelloWorld'
import PostList from '#/components/PostList'
import PostDetails from '#/components/PostDetails'
Vue.use(Router)
export default new Router(
{ routes: [
{ path: '/', redirect: { name:'PostList' }},
{ path: '/postlist', name: 'PostList', component: PostList },
{ path: '/postdetails/:id', name: 'PostDetails', component: PostDetails }
]}
)

why beforeRouteUpdate is not updating route

I am showing the products page use vueJS and API.
I am showing categories on the left side and products on the right side
but while clicking on category products should be shown and the same component should be a load but the same component is not loading
Please check why beforeRouteUpdate is not working
below is my code
<template>
<div style="padding: 25px">
<div class="container">
<div class="row">
<div class="col-md-3">
<div class="row">
<h3>Categories</h3>
<div class="col-md-12" align="left">
<router-link type="button" align="left" class="card-link" :to="'/'">
All Products</router-link>
</div>
<div class="col-md-12" v-for="category in categories" :key="category.id">
<CategoryCard :category="category" />
</div>
</div>
</div>
<div class="col-md-9">
<h1 class="index-msg">
{{ msg }}
</h1>
<div v-if="isShowId">
<span>props id:{{id}}</span>
<br />
<span>route.params id: {{uid}}</span>
</div>
<div class="row">
<div class="col-md-4" v-for="product in products" :key="product.id">
<ProductCard :product="product" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {
mapActions,
mapGetters
} from "vuex";
import CategoryCard from "../../components/products/CategoryCard";
import ProductCard from "../../components/products/ProductCard";
export default {
name: "Products",
props: ["id"],
computed: {
...mapGetters("product", ["products"]),
...mapGetters("category", ["categories"]),
},
components: {
ProductCard,
CategoryCard,
},
data() {
return {
msg: "Products Page",
uid: "",
isShowId: false,
};
},
methods: {
...mapActions("product", ["getProducts", "addCart", "removeCart"]),
...mapActions("category", ["getCategories"]),
},
mounted() {
let id = this.$route.params.id;
if (id) {
this.isShowId = true;
this.uid = id;
} else {
this.isShowId = false;
this.uid = "";
}
this.getProducts(id);
this.getCategories();
},
beforeRouteUpdate (to, from, next) {
next();
},
beforeRouteLeave (to, from, next) {
console.log(`Leave to =`, to);
console.log(`Leave from =`, from);
next();
},
};
</script>
Try to check what's the 'pathPre' variable here. This will help you finding where to route now.
beforeRouteUpdate: function (to, from, next) {
let pathPre = from.path.split("/");
console.log(pathPre)
next();
},
And if you want to just reload the route on the same url then use
this.$router.go();
Edited
Load with variable as slug
Try to use slug with route
{
path: '/routeA/:slug',
name: 'routeName',
component: routeA,
props:true,
}
and push to url with slug and route name
this.$router.push({
name: "routeName",
params: { slug: passSlugVariable },
});
and catch this prop in data as variable
data: function () {
return {
slug: this.$route.params.slug,
};
}
watch: {
'$route'(to, from) {
let to_path = to.path;
let from_path = from.path;
console.log(`to =`, to_path);
console.log(`from =`, from_path);
if (to_path !== from_path) {
this.$router.push(to_path)
this.$router.go();
} else {
this.$router.go();
}
},
},
This is working as I am refreshing the page every time using this.$router.go(); , I know this is wrong method , please tell me correct solution

Display an random Object from API Vue.js

I need to display a random object from an array. The array comes from the API.
<template>
<div class="container">
<div class="box">
<p class="name"> {{chosenName.title}} </p>
<p class="description"> {{chosenName.description}} </p>
<hr>
<img v-bind:src="chosenName.urlToImage" alt="">
<p class="author"> {{chosenName.author}} </p>
</div>
<div class="box">
<p class="name"> {{chosenName.title}} </p>
<p class="description"> {{chosenName.description}} </p>
<hr>
<img v-bind:src="chosenName.urlToImage" alt="">
<p class="author"> {{chosenName.author}} </p>
</div>
<button v-on:click="choose" id="choose-button">One more time</button>
</div>
</template>
#Options({
props: {
msg: String
},
data() {
return {
artworks: [],
errors: [],
chosenName: '',
}
},
created() {
axios.get(url)
.then(response => {
this.artworks = response.data.names;
})
.catch(e => {
this.errors.push(e)
})
},
methods: {
choose() {
const chosenNumber = Math.floor(Math.random() * this.artworks.length);
this.chosenName = this.artworks[chosenNumber];
console.log(chosenNumber)
}
}
})
I managed to display the random object on click. What I would like to have is the object appearing at page load. I tried to put the function in the mounted cycle but with no good result like this ---> this.choose();
you need to call this.choose() in your mounted/created hook after the promise is fulfilled if you want to make it appear on the page load as well. e.g:
mounted () {
axios.get(url)
.then(response => {
this.artworks = response.data.names;
this.choose()
})
.catch(e => {
this.errors.push(e)
})
}

infinite loop when create infinite scroll using vue.js and laravel

good day; kindly need your support to finalize this issue i try to make infinite scrolle using laravel and vue.js and my proplem is get in finite loop to set request to data base and mu applocation hang up this is my code x component
<template>
<div class="container" style="margin-top:50px;">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header"><strong> Laravel Vue JS Infinite Scroll - ItSolutionStuff.com</strong></div>
<div class="card-body">
<div>
<p v-for="item in list">
<a v-bind:href="'https://itsolutionstuff.com/post/'+item.slug" target="_blank">{{item.title}}</a>
</p>
<infinite-loading #distance="1" #infinite="infiniteHandler"></infinite-loading>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
alert()
console.log('Component mounted.')
},
data() {
return {
list: [],
page: 1,
};
},
methods: {
infiniteHandler($state) {
let vm = this;
this.$http.get('/Services?page='+this.page)
.then(response => {
return response.json();
}).then(data => {
$.each(data.data, function(key, value) {
vm.list.push(value);
});
$state.loaded();
});
this.page = this.page + 1;
},
},
}
</script>
this is my route
Route::get('/Services', 'ServicesController#Services');
Problem 1
You are binding to the distance property wrong.
Solution
Instead of <infinite-loading #distance="1" #infinite="infiniteHandler"></infinite-loading>
it should be
<infinite-loading :distance="1" #infinite="infiniteHandler"></infinite-loading>
Problem 2
In the code, this.page is being incremented before $http.get is resolved.
This may result in unintentional side effects.
Solution
As per the example in docs vue-infinite-loading hacker news example you should be incrementing the page after data is loaded.

Send data from one component to another in vue

Hi I'm trying to send data from one component to another but not sure how to approach it.
I've got one component that loops through an array of items and displays them. Then I have another component that contains a form/input and this should submit the data to the array in the other component.
I'm not sure on what I should be doing to send the date to the other component any help would be great.
Component to loop through items
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Name</p>
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in entries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry />
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
}
}
</script>
Component for adding / sending data
<template>
<div
class="entry-add"
v-bind:class="{ 'entry-add--open': addEntryIsOpen }">
<input
type="text"
name="addEntry"
#keyup.enter="addEntries"
v-model="newEntries">
</input>
<button #click="addEntries">Add Entries</button>
<div
class="entry-add__btn"
v-on:click="openAddEntry">
<span>+</span>
</div>
</div>
</template>
<script>
export default {
name: 'add-entry',
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push(this.newEntries);
this.newEntries = '';
},
openAddEntry() {
this.addEntryIsOpen = !this.addEntryIsOpen;
}
}
}
</script>
Sync the property between the 2:
<add-entry :entries.sync="entries"/>
Add it as a prop to the add-entry component:
props: ['entries']
Then do a shallow merge of the 2 and emit it back to the parent:
this.$emit('entries:update', [].concat(this.entries, this.newEntries))
(This was a comment but became to big :D)
Is there a way to pass in the key of name? The entry gets added but doesn't display because im looping and outputting {{ entry.name }}
That's happening probably because when you pass "complex objects" through parameters, the embed objects/collections are being seen as observable objects, even if you sync the properties, when the component is mounted, only loads first level data, in your case, the objects inside the array, this is performance friendly but sometimes a bit annoying, you have two options, the first one is to declare a computed property which returns the property passed from the parent controller, or secondly (dirty and ugly but works) is to JSON.stringify the collection passed and then JSON.parse to convert it back to an object without the observable properties.
Hope this helps you in any way.
Cheers.
So with help from #Ohgodwhy I managed to get it working. I'm not sure if it's the right way but it does seem to work without errors. Please add a better solution if there is one and I'll mark that as the answer.
I follow what Ohmygod said but the this.$emit('entries:update', [].concat(this.entries, this.newEntries)) didn't work. Well I never even need to add it.
This is my add-entry.vue component
<template>
<div
class="add-entry"
v-bind:class="{ 'add-entry--open': addEntryIsOpen }">
<input
class="add-entry__input"
type="text"
name="addEntry"
placeholder="Add Entry"
#keyup.enter="addEntries"
v-model="newEntries"
/>
<button
class="add-entry__btn"
#click="addEntries">Add</button>
</div>
</template>
<script>
export default {
name: 'add-entry',
props: ['entries'],
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push({name:this.newEntries});
this.newEntries = '';
}
}
}
</script>
And my list-entries.vue component
<template>
<div class="container-flex">
<div class="wrapper">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Competition Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredEntries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry :entries.sync="entries"/>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
import pickWinner from '#/components/pick-winner.vue'
export default {
name: 'entry-list',
components: {
addEntry,
pickWinner
},
data: function() {
return {
search: '',
entries: [
{
name: 'Geoff'
},
{
name: 'Stu'
},
{
name: 'Craig'
},
{
name: 'Mark'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredEntries() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
</script>