Access nested nodes with GraphQL (Nuxt) - javascript

i’m experiencing some issues with Apollo, GraphQL and Nuxt. i don’t know if it’s especially related to Nuxt though, or with vue.
i’m trying to use WordPress as headless CMS via WP-GraphQL plugin. here’s my query
WP-GraphQL interface
i basically created a graphql folder with a posts.js file inside, that contains my query
import gql from 'graphql-tag'
export const myQuery = gql`
query myQuery {
posts {
nodes {
id
title
date
slug
author {
node {
name
}
}
featuredImage {
node {
uri
sourceUrl
srcSet
}
}
}
}
}
`
then, all i need to do is to print my data in the template. here's the script part first.
<script>
import { myQuery } from '~/graphql/posts'
export default {
data() {
return {
posts: [],
}
},
apollo: {
posts: {
prefetch: true,
query: myQuery,
},
},
watch: {
async $route() {
await this.$nuxt.refresh()
window.scrollTo(0, 0)
},
},
transition: 'home',
async mounted() {
this.posts = await this.$apollo.query({ query: myQuery })
this.posts = this.posts.data.posts.nodes
this.loading = false
}
</script>
and then comes the template :
<template>
<section class="featured-projects">
<div class="featured-projects__wrapper">
<article v-for="post in posts" :key="post.id">
<p>{{ post.id }}</p>
<h2>{{ post.title }}</h2>
<span>{{ post.date }}</span>
</article>
</div>
</section>
</section>
</template>
everything just works!
now, i would like to print post author name as well. i first tried this :
<span>{{ post.author }}</span>
and this actually prints this :
{
"node": {
"name": "max max",
"__typename": "User"
},
"__typename": "NodeWithAuthorToUserConnectionEdge"
}
it totally makes sense, as author is an object with nested items in it. so according to what i’m being returned and following GraphQL API structure, to display post author name, think i should do something like this instead :
<span>{{ post.author.node.name }}</span>
and here’s the error i get, and i don’t know what to do to access what i want :
Cannot read property 'node' of undefined.

your problem arises from reading the data before it is loaded.
depending on your js settings you should be able to use one of the following:
<span>{{ post?.author.node.name }}</span>
or <span>{{ post ? post.author.node.name : '' }}</span>
according to the Vue Apollo documentation it could also be a problem with the duplication of the query
<script>
import { myQuery } from '~/graphql/posts'
export default {
data() {
return {
posts: [], // initialization
}
},
apollo: {
posts: {
prefetch: false, // to prevent SSR
query: myQuery,
update: data => {
console.log('overwrite posts with new data', data.posts)
return data.posts
}
},
}
}
</script>
further as there seem to be cases in which the author has more than one entry (perhaps co authors?) I would try to update the author rendering to the following:
<template>
<section class="featured-projects">
<div class="featured-projects__wrapper">
<article v-for="post in posts" :key="post.id">
<p>{{ post.id }}</p>
<div v-if="Array.isArray(post.author)">
first author is: {{ post.author[0].node.name }}
</div>
<div v-else-if="post.author">
author is: {{ post.author.node.name }}
</div>
<div v-else="post.author">
no author
</div>
</article>
</div>
</section>
</section>
</template>

Related

How to make an intersection between 2 datasets in Nuxt?

I tried to get data from api with params which come from an argument in a v-for.
In findUser method, I can console.log the data I'm looking for. But I can't get it at the end of findUser, why?
I know there is an async method to get it but I don't understand how to manage it to make it work with what I want to do;
I also thought about calling the two API at the same time, but the result is the same, I don't know how to manage it.
<template>
<div>
<h4>Listes Reçues</h4>
<p v-for="element in results" id="flex-list" :key="element.list_id">
{{ element.list_name }} de {{ findUser(element.user_id) }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
results: '',
nickname: '',
}
},
created() {
this.$axios
.get(`/api/listReceived/${this.$auth.user[0].user_id}`)
.then((res) => {
this.results = res.data
console.log(JSON.stringify(this.results))
})
.catch((err) => {
console.error(err)
})
},
methods: {
findUser(id) {
console.log(id)
let data = ''
this.$axios
.get(`http://localhost:3000/api/userdata/${id}`)
.then((res) => {
data = res.data[0].nickname
console.log(data)
})
.catch((err) => {
console.error(err)
})
return data
},
},
}
</script>
On top of my top answer which was quite not on point regarding the question but still relevant, here is an example on how to handle an intersection properly.
I did not used an endpoint but mocked the data locally in data() hence why I keep my post above.
<template>
<div class="flex flex-col items-center">
<h1 class="p-4 bg-green-700 rounded-md">
List of users ordered by their according message
</h1>
<!-- <pre>{{ messages }}</pre> -->
<section>
<div v-for="user in groupedMessages" :key="user.id" class="mt-4">
<p>
User: <b>{{ user.name }}</b>
</p>
<aside>
Messages:
<span v-if="!user.messages.length">No messages actually</span>
</aside>
<p v-for="message in user.messages" :key="message.id">
<span class="italic">- {{ message.text }}</span>
</p>
</div>
</section>
</div>
</template>
<script>
// ES version of lodash, lighter overall
import { cloneDeep } from 'lodash-es'
export default {
name: 'Index',
data() {
return {
messages: [
{
id: 1,
text: 'Hello world',
userId: 1,
},
{
id: 2,
text: 'Nice cool message',
userId: 1,
},
{
id: 3,
text: 'Still for the first user?',
userId: 1,
},
{
id: 4,
text: 'Yep, apparently...',
userId: 1,
},
{
id: 5,
text: "Eh, surprise, I'm a sneaky one...",
userId: 3,
},
{
id: 6,
text: 'Oh, a second one.',
userId: 2,
},
{
id: 7,
text: "You're damn right!!",
userId: 2,
},
],
users: [
{
name: 'Patrick',
id: 1,
messages: [],
},
{
name: 'Pablo',
id: 2,
messages: [],
},
{
name: 'Unkown author',
id: 5,
messages: [],
},
{
name: 'Escobar',
id: 3,
messages: [],
},
],
}
},
computed: {
groupedMessages() {
// we use that to avoid any kind of further mutation to the initial `users` array
const clonedUsers = cloneDeep(this.users)
// we do loop on each message and find a corresponding user for it
this.messages.forEach((message) =>
clonedUsers.forEach((user) => {
if (user.id === message.userId) {
user.messages.push(message)
}
})
)
return clonedUsers
},
},
}
</script>
The github repo is available, please do not pay attention to the unrelated name of it.
This is how it looks on Netlify.
created() is totally fine with Vue but usually you do use fetch() and asyncData() hooks in Nuxt.
Here is the basic idea using JSONplaceholder's API.
Here is a possible /pages/index.vue
<template>
<div class="flex flex-col items-center">
<h1 class="p-4 bg-green-700 rounded-md">
List of users from JSONplaceholder
</h1>
<section class="mt-4">
<p v-for="user in users" :key="user.id">
{{ user.name }} 🚀 {{ user.email }} ~
<nuxt-link
:to="{ name: 'users-id', params: { id: user.id } }"
class="border-b-4 border-green-500"
>
Check details
</nuxt-link>
</p>
</section>
</div>
</template>
<script>
export default {
name: 'Index',
data() {
return {
users: [],
}
},
async fetch() {
this.users = await this.$axios.$get('/users')
},
}
</script>
<style>
* {
#apply bg-gray-800 text-gray-100;
}
</style>
And the detailed page aka /pages/users/_id.vue using the fetch() hook.
<template>
<div class="flex flex-col items-center">
<nuxt-link class="p-4 bg-purple-700 rounded-md" :to="{ name: 'index' }">
Go back
</nuxt-link>
<h2>User details ID: # {{ $route.params.id }}</h2>
<p v-if="$fetchState.pending">Loading user's info...</p>
<p v-else-if="$fetchState.error">Error while fetching user</p>
<div v-else>
<p>{{ user.name }}</p>
<p>{{ user.phone }}</p>
<p>{{ user.website }}</p>
<p>{{ user.company.name }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'UserId',
data() {
return {
user: {},
}
},
async fetch() {
this.user = await this.$axios.$get(`/users/${this.$route.params.id}`)
},
}
</script>
I do prefer this approach because it's not blocking the render, you can add some smooth skeleton to still let the user know that something is happening. On top of that, fetch() is available on both components and pages while asyncData() is only for pages.
It also gives the nice $fetchState helper that can also be quite handy!
Here is the same /pages/users/_id.vue page using the asyncData() hook.
<template>
<div class="flex flex-col items-center">
<nuxt-link class="p-4 bg-purple-700 rounded-md" :to="{ name: 'index' }">
Go back
</nuxt-link>
<p>{{ user.name }}</p>
<p>{{ user.phone }}</p>
<p>{{ user.website }}</p>
<p>{{ user.company.name }}</p>
</div>
</template>
<script>
export default {
name: 'UserId',
async asyncData({ route, $axios }) {
const user = await $axios.$get(`/users/${route.params.id}`)
return { user }
},
}
</script>
Main benefit of using asyncData is the fact that it's more safe and that it's blocking the render (can be either a pro or a con, more of a con for me personally).
Here are some other in-depth answers comparing fetch() vs asyncData().
Check out this handy blog article on the subject and also this dev.to clone example.
Finally, if you want to take the SSG path and optimize the whole thing with the least amount of API calls once on the client-side, you can also check my other answer.

Vue Bootstrap Pagination Define :Total-rows

I am learning to paginate data returned from an API using AXIOS. I have a working set of code, but there is a place in the code defined by bootstrap for :Total-rows, this is currently hardcoded but this creates extra rows based on the value rather than a computed value. I want to calculate the number of rows dynamically.
I know that I can count the response data from the api using: this.variable = response.data.length, but the way I am calling the data is using page variable to paginate.
Any suggestions on an efficient way to accomplish this somewhat seemingly simple call?
<template>
<div id="app">
<div class="row">
<div class="col-md-12">
<li v-for="item in todos" :key="item.id">
{{ item.name }} : {{ item.type }}
</li>
</div>
</div>
<b-pagination size="md" :total-rows="54" v-model="currentPage" :per-page="10" #input="getPostData(currentPage)">
</b-pagination>
</div>
</template>
VUE
<script>
//Import axios for REST API calls
import axios from 'axios'
import 'regenerator-runtime/runtime';
//Import bootstrap CSS
import 'bootstrap/dist/css/bootstrap.css'
//Import bootstrap vue CSS
import 'bootstrap-vue/dist/bootstrap-vue.css'
const baseURL = 'http://localhost:3000/todos?_page='+this.currentPage+'&_limit='+this.limit;
export default {
name: 'app',
data () {
return {
title: 'Vue.js Pagination Example With Bootstrap',
currentPage: 1,
limit: 5,
todos: [],
todoName: "",
todoType: "",
}
},
methods: {
// Fetches todos when the component is created.
getPostData (currentPage) {
axios.get('http://localhost:3000/todos?_page='+this.currentPage+'&_limit='+this.limit)
.then(response => {
//console.log(response)
// JSON responses are automatically parsed.
this.todos = response.data
})
.catch(e => {
this.errors.push(e)
})
},
async addTodo() {
const res = await axios.post(baseURL, {
name: this.todoName,
type: this.todoType,
});
this.todos = [...this.todos, res.data];
//resets the input field
this.todoName = "";
this.todoType = "";
},
}, //end of methods
//detects the current page on load
mounted(currentPage){
this.getPostData(currentPage)
}
}
</script>
You will need the API to return the total amount of rows, otherwise your frontend have no way of knowing how many pages to show.
You can find an example of this below, which use a dummy/testing API called reqres. This API returns various information, like the current page, total amount of rows and per page and of course the data for the requested page.
new Vue({
el: "#app",
data() {
return {
currentPage: 1,
totalRows: 0,
perPage: 0,
users: [],
request: null
}
},
methods: {
async getData(page) {
const response = await fetch(`https://reqres.in/api/users?page=${page}&per_page=3`).then(resp => resp.json())
this.perPage = response.per_page;
this.users = response.data;
this.totalRows = response.total;
// Only for testing purposes
this.request = response
}
},
created() {
this.getData(this.currentPage)
}
})
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap#4.5.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.18.1/bootstrap-vue.min.css" />
<script src="//cdn.jsdelivr.net/npm/vue#2.6.12/dist/vue.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.18.1/bootstrap-vue.min.js"></script>
<div id="app">
<b-pagination
v-model="currentPage"
:total-rows="totalRows"
:per-page="perPage"
#change="getData">
</b-pagination>
<ul>
<li v-for="{ first_name, last_name } in users">
{{ first_name }} {{ last_name }}
</li>
</ul>
Request
<pre>{{ request }}</pre>
</div>

VueJs access data in array

I find myself facing a problem.
I need to retrieve a parameter in the url to display data dynamically but I can't find the solution.
what parameter must enter in state.event.aftermovies [] to have access to the id of my object.
If anyone has a solution, thanks
<template>
<div>
<div class="container text-center">
<div>
<h1>{{ aftermovie.id }}</h1>
<h1>{{ aftermovie.name }}</h1>
<h1>{{ aftermovie.slug }}</h1>
<h1>{{ aftermovie.video }}</h1>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
async fetch({ store, error, params }) {
try {
await store.dispatch('fetchEvent', params.id)
} catch (e) {
error({
statusCode: 503,
message: 'Unable to fetch event #' + params.id,
})
}
},
//Here is my problem
computed: mapState({
aftermovie: (state) => state.event.aftermovies[??????????????????]
}),
head() {
return {
title: this.aftermovie.name
}
}
}
</script>
and I would like to access the id of my object
event:Object
address:"Belgium"
aftermovies:Array[1]
0:Object
event_id:1
festival_id:""
id:4
name:"reverze 2020"
slug:"reverze-2020"
what is the right way ??
Assuming aftermovies is an array containing objects like:
aftermovies: [{
event_id:1,
festival_id:"",
id:4,
name:"reverze 2020",
slug:"reverze-2020"
}]
you could do something like aftermovies[0].id
Here is a jsfiddle example

Issue trying to output WordPress post data with Vue

Can anyone tell me why I am getting an error when trying to output the post data?
I've tried adding params like title, content but nothing works. I might add that I can get it to work using the id.
<template>
<section class="container animated fadeIn">
<nuxt-link :to="{ name: 'blog' }">
Back to Blog
</nuxt-link>
<div>
<h1 class="title">
{{ post }} <!-- this returns an array of the post data -->
{{ slug }} <!-- this returns the slug -->
{{ post.title }} <!-- this returns an error _vm.post is undefined -->
</h1>
</div>
</section>
</template>
<script>
import axios from 'axios'
export default {
name: 'post',
data () {
return {
slug: this.$route.params.slug,
post: {},
error: []
}
},
asyncData ({ params }) {
return axios.get(`http://localhost/amcdwp/wp-json/wp/v2/posts/?slug=${params.slug}`)
.then((response) => {
return { post: response.data }
})
}
}
</script>
I want to output individual data like post.title. I get an error of _vm.post is undefined

Displaying related table data in vuejs

I'm learning vuejs and using laravel for the backend api. I have the following tables
articles, articles_translations and i mentioned their relations in their models
i created a vue component fpr articles
<template>
<div>
<div v-for="article in articles" :key="article.articles">
<h4>{{article.translations}}</h4>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { mapGetters } from 'vuex'
export default {
layout: 'basic',
computed: mapGetters({
locale: 'lang/locale'
}),
data: function () {
return {
articles: []
}
},
mounted() {
var app = this;
console.log(this.locale);
axios.get('/api/articles')
.then(response => {
// JSON responses are automatically parsed.
this.articles = response.data
})
.catch(e => {
this.errors.push(e)
})
}
}
</script>
this displays [ { "id": 1, "article_id": 1, "title": "This is an example title", "subtitle": "this is a subtitle", "content": "this is the content", "locale": "en", "created_at": null, "updated_at": null } ] as expected
I want to display the articles in this format
article title article
article subtitle
article content
In blade i normally do {{ $article->article_translations->title }} to fetch related table data. How does this work in vue? How to display the data in the format i mentioned.
Judging by what you posted you could get the article translation attributes like this:
<div v-for="article in articles" :key="article.articles">
<div v-for="translation in article.translations">
<h4>{{ translation.title }}</h4>
{{ translation.subtitle }}
{{ translation.content }}
</div>
</div>
Basically add another for loop, since your translations are in an array. And then simply display the attributes.
just use the Laravel query builder the inner join will help to your problem this is a quick example then you will receive the collection of properties or your object.
explanation: users table get a User object, people table is related to the user with user_id column. just need to join tables related and get this result
$users = DB::table('people')
->join('users', 'users.id', '=' ,'people.user_id')
->select('users.*',
'first_name',
'last_name',
'street_address',
'city',
'state',
'contact_phone'
)->get();
return response()->json(['data' => $users], 200);

Categories

Resources