Cannot read properties of undefined (reading ...) Apollo + GraphQL + Vue 3 - vue.js

I'm having some trouble accessing data from my GraphQL query.
This is the situation.
In my MenuList.vue when I try to access menu.menuItems.nodes I receive the error
Cannot read properties of undefined (reading 'nodes')
But if I try to access menu.menuItems it works...
Have you any idea why this is happening?
main.js
import { createApp, provide, h } from "vue";
import { ApolloClient, HttpLink, InMemoryCache } from "#apollo/client/core";
import { createApolloProvider } from "#vue/apollo-option";
import App from "./App.vue";
const httpLink = new HttpLink({
uri: "http://XXX.XXX.XXX.XXX/graphql",
});
// Create the apollo client
const apolloClient = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
connectToDevTools: true,
});
// Create a provider
const apolloProvider = createApolloProvider({
defaultClient: apolloClient,
});
const app = createApp({
render: () => h(App),
});
app.use(apolloProvider);
app.mount("#app");
graphql.js
import gql from "graphql-tag";
export const MENU_QUERY = gql`
query MENU_QUERY {
menu(id: "MainMenu", idType: NAME) {
count
id
databaseId
name
slug
menuItems {
nodes {
id
databaseId
title
url
uri
cssClasses
description
label
linkRelationship
target
parentId
}
}
}
}
`;
App.vue
<template>
<div id="app">
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
<div class="container">
<div class="navbar-brand">
<menu-list></menu-list>
</div>
</div>
</nav>
<router-view/>
</div>
</template>
<script>
import MenuList from './components/MenuList'
export default {
name: 'App',
components: {
MenuList
}
}
</script>
MenuList.vue
<template>
<div>
MENU LIST
<h4 v-if="loading">Loading...</h4>
{{menu.menuItems.nodes}}
</div>
</template>
<script>
import { MENU_QUERY } from "#/graphql";
export default {
name: "MenuList",
data() {
return {
menu: [],
loading: 0,
};
},
apollo: {
menu: {
query: MENU_QUERY,
},
},
};
</script>
Data returned by GraphQL
{
"__typename":"Menu",
"count":2,
"id":"dGVybToz",
"databaseId":3,
"name":"MainMenu",
"slug":"mainmenu",
"menuItems":{
"__typename":"MenuToMenuItemConnection",
"nodes":[
{
"__typename":"MenuItem",
"id":"cG9zdDo2",
"databaseId":6,
"title":null,
"url":"http://XXX/page-3/",
"uri":"/page-3/",
"cssClasses":[
],
"description":null,
"label":"Page 3",
"linkRelationship":null,
"target":null,
"parentId":null
},
{
"__typename":"MenuItem",
"id":"cG9zdDoxMA==",
"databaseId":10,
"title":null,
"url":"http://XXX/page-2/",
"uri":"/page-2/",
"cssClasses":[
],
"description":null,
"label":"Page 2",
"linkRelationship":null,
"target":null,
"parentId":null
}
]
}
}

try:
//add code
<p v-if="menu && menu.menuItems">
{{menu.menuItems.nodes}}

Related

vue graphql composition api :data binding

//This is my HomeView
<template>
<div class="home">
<div class="error" v-if="error">Error: {{error.message}}</div>
<div v-else> <BlogList :allBlogs="allBlogs" /> </div>
</div>
</template>
<script>
import { useQuery, useResult } from '#vue/apollo-composable'
import gql from 'graphql-tag'
import BlogList from '../components/BlogList.vue'
// # is an alias to /src
const ALL_BLOGS = gql`
query{
allBlogs{
id
title
author
body
yearPublished
}
}`
export default {
name: 'HomeView',
components: { BlogList },
setup() {
const {result, error } = useQuery(ALL_BLOGS)
// We use use result for nested queries
const allBlogs = useResult(result, null, data => data.allBlogs)
return { allBlogs, error}
}
}
</script>
<style></style>
//This is my BLogList
<template>
<div v-for="blog in allBlogs" :key="blog.id">
<SingleBlog :blog='blog' />
</div>
</template>
<script>
import SingleBlog from '../components/SingleBlog.vue'
export default {
props: ['allBlogs'],
components: { SingleBlog },
}
</script>
<style></style>
//This is my SingleBlog
<template>
<router-link :to="{name: 'Details', params: {id: blog.id}}"><h1>{{blog.title}}</h1></router-link>
<p>{{snippet}}</p>
</template>
<script>
import { computed } from '#vue/runtime-core'
export default {
props: [ 'blog' ],
setup(props) {
const snippet = computed(() => {
return props.blog.body.substring(0,100) + '....'
})
return { snippet }
}
}
</script>
<style></style>
//This is my Details view
<template>
{{blog.title}}
</template>
<script>
import { useQuery, useResult } from '#vue/apollo-composable'
import gql from 'graphql-tag'
export default {
props: ['id'],
setup(props) {
const {result, error} = useQuery(gql`
query allBlogs($id: ID!){
allBlogs(id: $id){
id
title
author
body
yearPublished
}
}
`, props)
const blog = useResult(result)
return {blog, error }
}
}
</script>
<style></style>
In the above code everything works fine, until i get to Details view. The fetching graphql api (django backend) for list of blog I've created works fine. However, trying to see the detail of the blog which has been router-linked to Singleblog is not working. I tried to use the example provided on vue apollo site.
Does anyone have any idea what might be the problem is with my code at Details.vue?

Watch doesn't get fire/trigger when global property change

I’m very new to Vue and I begin with Vue 3. I was trying to migrate a template from Vue 2 to Vue 3 so I can start with my project.
I have this plugin file.
Sidebar\Index.ts
import SidebarPlugComp from './SidebarPlugComp.vue'
import SidebarLinkPlugComp from './SidebarLinkPlugComp.vue'
// tiny internal plugin store
const SidebarStore = {
showSidebar: false,
sidebarLinks: [],
displaySidebar (value: boolean) {
this.showSidebar = value
}
}
const SidebarPlugin = {
install (app: any) {
app.config.globalProperties.$sidebar = SidebarStore
app.component('side-bar-plug-comp', SidebarPlugComp)
app.component('sidebar-link-plug-comp', SidebarLinkPlugComp)
}
}
export default SidebarPlugin
Also I have a BaseTopNavLay layout file so I can toggle the sidebar with handleSidebarToggle onclick button method
<template>
\\...
<div class="navbar-toggle d-inline" :class="{toggled: $sidebar.showSidebar}">
<button type="button"
class="navbar-toggler"
aria-label="Navbar toggle button"
#click.prevent="handleSidebarToggle">
<span class="navbar-toggler-bar bar1"></span>
<span class="navbar-toggler-bar bar2"></span>
<span class="navbar-toggler-bar bar3"></span>
</button>
\\ ...
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { ModalComp } from '../components'
export default defineComponent({
name: 'BaseTopNavLay',
components: {
ModalComp
},
// ...
methods: {
handleSidebarToggle (): void {
this.$sidebar.displaySidebar(!this.$sidebar.showSidebar)
},
handleHideSideBar (): void {
this.$sidebar.displaySidebar(false)
},
}
})
</script>
And here is the watch in the App.vue file
<template>
<component :is="this.$route.meta.layout || 'div'">
<router-view />
</component>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Application',
methods: {
toggleNavOpen () {
console.log('here')
let root = document.getElementsByTagName('html')[0]
root.classList.toggle('nav-open')
}
},
/*watch: {
'$sidebar.showSidebar': function(newVal, oldVal) {
console.log(newVal, oldVal)
}
}*/
mounted () {
//#ts-ignore
this.$watch('$sidebar.showSidebar', this.toggleNavOpen)
}
})
</script>
Wherever I test the var this.$sidebar.showSidebar I can access to its value properly. Also, the onclick method is changing the SidebarStore object in Sidebar\Index.ts plugin file.
Can anyone give me a hint what am I missing here? Why the watch doesn't get fired. Thanks in advance.
The problem is that you have not made your $sidebar reactive, and a watch needs to use a reactive variable.
You can keep the store where you have it, but I'd put it into a separate file (store.js) and import where needed, no need to put it on app.config.globalProperties.$sidebar (but that might be a personal preference
// store.js
// using reactive (all values)
export const SidebarStore = Vue.reactive({
showSidebar: false,
sidebarLinks: [],
})
// or using ref (one for each)
// export const showSidebar = Vue.ref(false);
export const displaySidebar = (value: boolean) => {
SidebarStore.showSidebar.value = value;
}
this will make SidebarStore and displaySidebar available anywhere in your code
use like this
<template>
\\...
<div class="navbar-toggle d-inline" :class="{toggled: $sidebar.showSidebar}">
<button type="button"
class="navbar-toggler"
aria-label="Navbar toggle button"
#click.prevent="handleSidebarToggle">
<span class="navbar-toggler-bar bar1"></span>
<span class="navbar-toggler-bar bar2"></span>
<span class="navbar-toggler-bar bar3"></span>
</button>
\\ ...
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { ModalComp } from '../components'
import { SidebarStore, displaySidebar } from '../store'
export default defineComponent({
name: 'BaseTopNavLay',
components: {
ModalComp
},
// ...
methods: {
handleSidebarToggle (): void {
displaySidebar(!SidebarStore.showSidebar)
},
handleHideSideBar (): void {
displaySidebar(false)
},
}
})
</script>

vue js passing data through global event bus not working

Using vue cli I have created a simple vue app with two nested components. I want to pass data between them clicking the h1 tag in my component 1 (a more structured approach suggests to use vuex but this is a very easy app passing simple data using for test).
Clicking the h1 I receive an error but I'm not getting the point. The error says
[Vue warn]: Error in event handler for "titleChanged": "TypeError: Cannot read property 'apply' of undefined"
(found in <Root>)
My code is below
main.js
import Vue from 'vue'
import App from './App.vue'
import Axios from 'axios'
Vue.config.productionTip = false
Vue.prototype.$http = Axios
export const bus = new Vue();
new Vue({
render: h => h(App),
}).$mount('#app')
app.vue
<template>
<div>
<comp-1 v-bind:title="title"></comp-1>
<comp-2 v-bind:title="title"></comp-2>
</div>
</template>
<script>
import comp-1 from './components/Comp-1.vue'
import comp-2 from './components/Comp-2.vue'
export default {
components: {
'comp-1': comp-1,
'comp-2': comp-2
},
data() {
return {
title: "my title"
}
}
}
</script>
<style scoped>
</style>
comp-1.vue
<template>
<header>
<h1 v-on:click="changeTitle">{{ pTitle }}</h1>
</header>
</template>
<script>
import {bus} from '../main'
export default {
props: {
title: {
Type: String
}
},
data() {
return {
pTitle: ''
}
},
created: function() {
this.pTitle = this.title
},
methods: {
changeTitle: function() {
this.pTitle = 'I have changed my title!'
bus.$emit('titleChanged', this.pTitle)
}
}
}
</script>
<style scoped>
</style>
comp-2.vue
<template>
<footer>
<p>{{ title }}</p>
</footer>
</template>
<script>
import {bus} from '../main'
export default {
props: {
title: {
Type: String
}
},
data() {
return {
pTitle: ''
}
},
created() {
this.pTitle = this.title;
bus.$on('titleChanged'), (data) => {
this.title = data
}
}
}
</script>
<style scoped>
</style>
In created of comp-2 component, there is a mistake in event handler
Change it like this:
bus.$on("titleChanged", data => {
this.title = data;
});

vuex module mode in nuxtjs

I'm trying to implement a todo list using modules mode in the vuex store in nuxtjs but get the error this.$store.todo is undefined and cant find much about this relating to nuxt
Can anyone assist please I have
store index.js
export const state = () => ({
})
export const mutations = {
}
store todo.js
export const state = () => ({
todos: [],
})
export const mutations = {
mutations ...
}
export const actions = {
actions ...
}
export const getters = {
getters ...
}
index.vue page
<template>
<div>
<h2>Todos:</h2>
<p> Count: {{ doneTodosCount }} </p>
<ul v-if="todos.length > 0">
<li v-for="(todo, i) in todos" :key="i">
...
</li>
</ul>
<p v-else>Done!</p>
<div class="add-todo">
<input type="text" v-model="newTodoText">
<button #click="add">Add todo</button>
</div>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
name: 'app',
data () {
return {
newTodoText: ""
}
},
created () {
this.$store.todo.dispatch('loadData')
},
computed: {
...mapState(['todos', ]),
...mapGetters([ 'doneTodosCount', 'doneTodos'])
},
methods: {
toggle (todo) {
this.$store.todo.dispatch('toggleTodo', todo)
},
}
}
</script>
From what i read I thought this should work but doesn't
I should add it all works fine if i don't use modules mode and just have a single index.js setup
Many Thanks
You need to call it differently
this.$store.dispatch('todo/toggleTodo', todo)
Also better to call it in fetch method, not created

vue, vuex, vue-i18n change language button event

Why changing mutation do not update page in new language?
main.js : here I implemented vue-i18n with vue:
import Vue from 'vue'
import VueRouter from 'vue-router'
import VueResource from 'vue-resource'
import VueI18n from 'vue-i18n'
import locales from './locales'
import router from './router'
import store from './store'
import App from './App'
Vue.use(VueRouter)
Vue.use(VueResource)
Vue.use(VueI18n, store)
Vue.http.interceptors.push((request, next) => {
console.log('sending request: ', request)
next(response => {
console.log('response: ', response)
})
})
Vue.config.debug = true
Vue.config.lang = 'fa'
Object.keys(locales).forEach(lang => {
Vue.locale(lang, locales[lang])
})
const app = new Vue({
el: '#app',
router,
VueI18n,
store,
render: h => h(App)
})
app.$mount('#app')
App.vue: Then used two buttons to change language:
<template>
<div id="app">
<h2>{{ $t('example', '#store.state.culture') }}</h2>
<p>{{ count }}</p>
<p>
<button #click="increment()">+</button>
<button #click="decrement()">-</button>
</p>
<p>culture: {{ culture }}</p>
<p>
<button #click=' changeCulture("en") '>English</button>
<button #click=' changeCulture("fa") '>پارسی</button>
</p>
<input type="text" v-model="newUserName">
<button #click="handleAddUserButton()">add</button>
<div>
<router-link to="/page1">Go to page1</router-link>
<router-link to="/page2">Go to page2</router-link>
</div>
<transition name="fade" mode="out-in">
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
<img src="./assets/logo.png">
<hello></hello>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
// import App from './main.js'
import Hello from 'components/Hello'
export default {
name: 'app',
components: {
Hello
},
data () {
return {
newUserName: ''
}
},
computed: {
...mapGetters([
'count',
'culture'
])
},
methods: {
...mapActions([
'increment',
'decrement',
'exampleGetFirebaseData',
'examplePostFirebaseData',
'changeCulture'
]),
handleAddUserButton () {
const user = {
name: this.newUserName
}
this.examplePostFirebaseData(user)
.then(resp => {
// console.log('resp: ', resp)
})
.catch(error => {
console.log('catch error: ', error)
})
},
handleError () {
}
},
beforeMount () {
this.exampleGetFirebaseData()
.then(resp => {
// console.log('resp: ', resp)
})
.catch(error => {
this.handleError(error)
// console.log('catch error: ', error)
})
}
}
</script>
sotre > culture.js: Then using store, getters, actions and mutation to change langauge,
const state = {
locales: ['en', 'fa'],
culture: 'en'
}
const getters = {
culture: state => state.culture
}
const actions = {
async changeCulture ({ commit }, playload) {
commit('CHANGE', playload)
}
}
import App from '../../main.js'
const mutations = {
CHANGE (state, payload) {
if (state.locales.indexOf(payload) !== -1) {
state.culture = payload
} else state.culture = 'en'
console.log(App.i18n)
}
}
export default {
state,
getters,
actions,
mutations
}
I checked using vue development tools in chrome and culture is changed but the problem is that title of {{ $t("example")}} do not change as mutation change.
I know doubt something basic is wrong in my code, may you please help.
Many Thanks in advance.
Becasue this is not a valid expression for i18n
<h2>{{ $t('example', '#store.state.culture') }}</h2>
If you want to translate the text en and fa do this
<h2>{{ $t(culture) }}</h2>
OR
<h2>{{ $t(`namespace:${culture}`) }}</h2>
If you want to use namespace