#javascript #vue.js #graphql #apollo
Вопрос:
Я учусь Vue
и борюсь с ошибкой браузера, к которой я понятия не имею, как подойти. Приложение использует Apollo и GraphQL. Ошибка возникает во время перехода к Posts.vue
компоненту, когда infiniteScrollPosts
запрос должен быть разрешен.
vue.runtime.esm.js:619 [Vue warn]: Error in created hook: "TypeError: Cannot read property 'watchQuery' of undefined"
vue.runtime.esm.js:1888 TypeError: Cannot read property 'watchQuery' of undefined
at DollarApollo.watchQuery (vue-apollo.esm.js:1256)
at SmartQuery.executeApollo (vue-apollo.esm.js:798)
at SmartQuery.start (vue-apollo.esm.js:561)
at SmartQuery.autostart (vue-apollo.esm.js:470)
at DollarApollo.addSmartQuery (vue-apollo.esm.js:1334)
at VueComponent.launch (vue-apollo.esm.js:1943)
at invokeWithErrorHandling (vue.runtime.esm.js:1854)
at callHook (vue.runtime.esm.js:4219)
at VueComponent.Vue._init (vue.runtime.esm.js:5008)
at new VueComponent (vue.runtime.esm.js:5154)
Это мой компонент:
<template>
<v-container v-if="infiniteScrollPosts">
<div v-for="post in infiniteScrollPosts.posts" :key="post._id">
<img :src="post.imageUrl" height="100" />
<h3>{{ post.title }}</h3>
</div>
<v-btn @click="showMorePosts" v-if="showMoreEnabled">Fetch More</v-btn>
</v-container>
</template>
<script>
import { INFINITE_SCROLL_POSTS } from "../../queries";
const pageSize = 2;
export default {
name: "Posts",
data() {
return {
pageNum: 1,
showMoreEnabled: true
};
},
apollo: {
infiniteScrollPosts: {
query: INFINITE_SCROLL_POSTS,
variables: {
pageNum: 1,
pageSize
}
}
},
methods: {
showMorePosts() {
this.pageNum ;
this.$apollo.queries.infiniteScrollPosts.fetchMore({
variables: {
pageNum: this.pageNum,
pageSize
},
updateQuery: (prevResult, { fetchMoreResult }) => {
console.log("previous result", prevResult.infiniteScrollPosts.posts);
console.log("fetch more result", fetchMoreResult);
const newPosts = fetchMoreResult.infiniteScrollPosts.posts;
const hasMore = fetchMoreResult.infiniteScrollPosts.hasMore;
this.showMoreEnabled = hasMore;
return {
infiniteScrollPosts: {
__typename: prevResult.infiniteScrollPosts.__typename,
posts: [...prevResult.infiniteScrollPosts.posts, ...newPosts],
hasMore
}
};
}
});
}
}
};
</script>
Запрос GraphQL
:
export const INFINITE_SCROLL_POSTS = gql`
query(
$pageNum: Int!,
$pageSize: Int!
) {
infiniteScrollPosts(
pageNum: $pageNum,
pageSize: $pageSize
) {
hasMore
posts {
_id
title
imageUrl
categories
description
likes
createdDate
messages {
_id
}
createdBy {
_id
userName
avatar
}
}
}
}
`;
Resolver:
module.exports = {
Query: {
(...)
infiniteScrollPosts: async (_, { pageNum, pageSize }, { postSchema }) => {
let posts;
if (pageNum === 1) {
posts = await postSchema.find({}).sort({ createdDate: "desc" }).populate({
path: "createdBy",
model: "User"
}).limit(pageSize);
} else {
const skips = pageSize * (pageNum - 1);
posts = await postSchema.find({}).sort({ createdDate: "desc" }).populate({
path: "createdBy",
model: "User"
}).skip(skips).limit(pageSize);
}
const totalPosts = await postSchema.countDocuments();
const hasMore = totalPosts > pageSize * pageNum;
return { hasMore, posts };
},
(...)
},
Mutation: {
(...)
}
};
TypeDefs:
(...)
type PostsPage {
posts: [Post]
hasMore: Boolean
}
type Query {
(...)
infiniteScrollPosts(pageNum: Int!, pageSize: Int!): PostsPage
}
(...)
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import Router from "vue-router";
import store from "./store";
import vuetify from "./plugins/vuetify";
import ApolloClient from "apollo-boost";
import VueApollo from "vue-apollo";
import FormAlert from "./components/Shared/FormAlert";
import "@babel/polyfill";
Vue.component("form-alert", FormAlert);
Vue.use(VueApollo);
export const apolloClient = new ApolloClient({
uri: "http://localhost:4000/graphql",
fetchOptions: {
credentials: "include"
},
request: operation => {
if (!localStorage.token) {
localStorage.setItem("token", "");
}
operation.setContext({
headers: {
authorization: localStorage.getItem("token")
}
});
},
onError: ({ graphQLErrors, networkError }) => {
if (networkError) {
console.log("[networkError]", networkError);
}
if (graphQLErrors) {
for (let err of graphQLErrors) {
console.dir(err);
if (err.name === "AuthenticationError") {
store.commit("setAuthError", err);
store.dispatch("signoutUser");
}
}
}
}
});
const apolloProvider = new VueApollo({ apolloClient });
Vue.config.productionTip = false;
new Vue({
apolloProvider,
router,
store,
vuetify,
render: h => h(App),
created() {
this.$store.dispatch("getCurrentUser");
}
}).$mount("#app");
const originalPush = Router.prototype.push;
Router.prototype.push = function(location) {
return originalPush.call(this, location).catch(ex => {
if (ex.name !== "NavigationDuplicated") {
throw ex;
}
});
};
Vue.use(router);
Would appreciate some help as you can consider me a Vue Noob :).
// EDIT
To answer the comments:
@xadm
The full list of requests present on Posts
component load contains only one significant call to to the GraphQL
and that’s my authorization — which works and is of no consequence here. The one that returns 204
, on the other hand, is present during every other component load and I am not familiar enough with Vue
to comment on it but since it is on all other pages then it can be discarded as irrelevant to the issue.
As for your react smiley face, I very much prefer to work with Blazor
.
@Adam Orlov
You can find an example of such usage here: https://apollo.vuejs.org/guide/apollo/pagination.html, you access $store
in the same manner. Regardless, it is not relevant to the problem, you can just comment that section out as it is not even called before the button is explicitly clicked which it isn’t, the problem seems to be here:
apollo: {
infiniteScrollPosts: {
query: INFINITE_SCROLL_POSTS,
variables: {
pageNum: 1,
pageSize
}
}
},
// EDIT 2
This is not Vue related error. It’s an Apollo client error. There is many similar errors in the internet about watchQuery and undefined with a different frameworks. It could be that you are using apollo to fast when page load, and it’s not initialized yet. – user1093555
Yeah I figured, I browsed these before posting here and found nothing that helped me to identify the problem. I thought that it might be too early in the lifecycle too but this precise code repeats in numerous tutorials and documentation pages, including the official one I linked before.
I don’t care about all requests … check only POST /graphql request AND response DETAILY (headers, body) — find the DIFFERENCE between working (?) first and following (fetchMore, next page) or working request in playground … I assume you don’t know react at all, you don’t see a difference/distance to all these ‘templatish’, quasi [not real]-component solutions – xadm
fetchMore
is irrelevant because the app is not even getting there yet, it requires user to click a button first. You can comment out the entire methods
section from my code, its not even getting to that point. I posted the screenshot of requests only for reference sake, I told you I checked the headers and bodies, the only request is the one that authorizes the user and it works (sends correct data, receives correct response). The one that should load the posts is not even called. And no, I never worked with React because I have never needed too, I learn Vue
to expand my knowledge though I don’t really need it either.
I believe the point of the tutorial I am following is to show that it is possible to use apollo directly with apollo: (...)
.
I am a Vue noob but I am not an idiot, it is easy to make it work with the following modifications:
Posts.vue
<template>
<v-container text-center v-if="posts amp;amp; posts.length > 0">
<div v-for="post in posts" :key="post._id">
<img :src="post.imageUrl" height="100" />
<h3>{{ post.title }}</h3>
</div>
<v-btn class="mt-3 info" @click="showMorePosts" v-if="hasMore" info>Fetch More</v-btn>
</v-container>
</template>
<script>
import { mapGetters } from "vuex";
const pageSize = 2;
export default {
name: "Posts",
data() {
return {
pageNum: 1
};
},
created() {
this.handleGetInfiniteScrollPosts();
},
computed: {
...mapGetters(["posts", "hasMore"])
},
methods: {
handleGetInfiniteScrollPosts() {
this.$store.dispatch("getInfiniteScrollPosts", {
pageNum: this.pageNum,
pageSize
});
},
showMorePosts() {
this.pageNum ;
this.handleGetInfiniteScrollPosts();
}
}
};
</script>
store.js
:
(...)
Vue.use(Vuex);
(...)
export default new Vuex.Store({
state: {
posts: [],
hasMore: true
(...)
},
mutations: {
(...)
setPosts: (state, payload) => {
state.posts = payload;
},
updatePosts: (state, payload) => {
state.posts = [...state.posts, ...payload];
},
setHasMore: (state, payload) => {
state.hasMore = payload;
},
(...)
},
actions: {
(...)
getInfiniteScrollPosts: ({ commit }, payload) => {
if (payload.pageNum === 1) {
commit("setPosts", []);
}
apolloClient.query({
query: INFINITE_SCROLL_POSTS,
variables: payload
}).then(({ data }) => {
commit("updatePosts", data.infiniteScrollPosts.posts);
commit("setHasMore", data.infiniteScrollPosts.hasMore);
});
},
(...)
},
getters: {
posts: state => state.posts,
hasMore: state => state.hasMore,
(...)
}
});
Доказательство:
https://www.dropbox.com/s/r6ol4htbu5503hs/VueInfinitePosts.mp4?dl=0
В этом случае оба соответствующих запроса graphql разрешаются с правильными телами ответов, и ошибок вообще нет. Однако это не относится к делу, потому что, как я уже сказал, этот пост не об этом.
Комментарии:
1. проверьте детали сетевого запроса [и ответа]… иди реагируй 😉
2. Я никогда не использовал apollo, но вы зарегистрировались
apollo
в компоненте, но пытаетесь связатьсяthis.$apollo
с ним с$
помощью . Это всего лишь предположение, но вы уверены, что это правильно?3. Большое вам обоим спасибо за комментарии, к сожалению, проблема остается. Я обновил сообщение, чтобы дать ответы.
4. Это не ошибка, связанная с Vue. Это ошибка клиента Apollo. В Интернете есть много подобных ошибок, связанных с watchQuery и неопределенных с помощью разных фреймворков. Возможно, вы используете apollo для быстрой загрузки страницы, и она еще не инициализирована.
5. Адресованы ваши комментарии, ребята, спасибо за ответы.