I am using nuxt and apollo together with: https://github.com/nuxt-community/apollo-module
I have a working GraphQL query (tested in GraphiQL):
(Because I want to fetch the info about my page and also some general SEO information)
{
entries(section: [pages], slug: "my-page-slug") {
slug
title
}
seomatic(uri: "/") {
metaTitleContainer
metaTagContainer
metaLinkContainer
metaScriptContainer
metaJsonLdContainer
}
}
I want to fetch this data as well with apollo in nuxt:
So I tried:
<script>
import page from '~/apollo/queries/page'
import seomatic from '~/apollo/queries/seomatic'
export default {
apollo: {
entries: {
query: page,
prefetch: ({ route }) => ({ slug: route.params.slug }),
variables() {
return { slug: this.$route.params.slug }
}
},
seomatic: {
query: seomatic,
prefetch: true
}
},
…
If I do that I will get an error message:
GraphQL error: Cannot query field "seomatic" on type "Query".
I then found this issue
https://github.com/apollographql/apollo-tooling/issues/648
and I would like to know if ths could be a problem of the apollo nuxt module.
Because following that fix indicated in the issue does not resolve anything.
I further tried to combine the two calls into one:
fragment SeoMaticFragment on Root {
seomatic(uri: "/") {
metaTitleContainer
metaTagContainer
metaLinkContainer
metaScriptContainer
metaJsonLdContainer
}
}
query myQuery($slug: String!) {
entries(section: [pages], slug: $slug) {
slug
title
}
SeoMaticFragment
}
~/apollo/queries/page.gql
But this would first throw an error
fragment Unknown type "Root"
So what is the best way to combine?
Why are the requests failing
is there an option to activate batching like described here: https://blog.apollographql.com/query-batching-in-apollo-63acfd859862
-
const client = new ApolloClient({
// ... other options ...
shouldBatch: true,
});
thank you so much in advance.
Cheers
There is actually a solution to this problem.
I found out that the result hook in vue-apollo solves this problem:
Example code that works:
<script>
import gql from 'graphql-tag'
const query = gql`
{
entries(section: [pages], slug: "my-example-page-slug") {
slug
title
}
seomatic(uri: "/") {
metaTitleContainer
metaTagContainer
metaLinkContainer
metaJsonLdContainer
}
}
`
export default {
data: () => {
return {
page: false,
seomatic: {}
}
},
apollo: {
entries: {
query,
prefetch: ({ route }) => ({ slug: route.params.slug }),
variables() {
return { slug: this.$route.params.slug }
}
},
result(result) {
this.entries = result.data.entries
this.seomatic = result.data.seomatic
}
}
}
</script>
Related
I'm trying to extend a mutation in Keystone 6, but having a lot of trouble just getting the standard DB update to work in a custom mutation resolver; Using the standard Keystone boilerplate and added a new collection/list.
Following the examples here, I've matched custom-schema.ts with the generated schema.graphql
schema.graphql (simplified):
type Dog {
id: ID!
name: String
}
input DogWhereUniqueInput {
id: ID
}
input DogUpdateInput {
name: String
}
type Mutation {
updateDog(
where: DogWhereUniqueInput!
data: DogUpdateInput!
): Dog
}
custom-schema.ts:
import { graphQLSchemaExtension } from '#keystone-6/core';
import { Context } from '.keystone/types';
export const extendGraphqlSchema = graphQLSchemaExtension<Context>({
typeDefs: `
type Mutation {
""" update a dog """
updateDog(
where: DogWhereUniqueInput!
data: DogUpdateInput!
): Dog
}
`,
resolvers: {
Mutation: {
updateDog: async (root, { where, id }, context) => {
try {
const response = await context.db.Dog.updateOne({
where: { id },
data: { name: 'updated name'}
});
return response;
} catch (updateError: any) {
throw updateError;
}
}}
}
},
);
keystone.ts:
import { extendGraphqlSchema } from './custom-schema';
// ...
export default withAuth(
config({
db: {
provider: 'sqlite',
url: 'file:./keystone.db',
},
ui: {
isAccessAllowed: (context) => !!context.session?.data,
},
lists,
session,
extendGraphqlSchema,
})
);
When I trigger an update from the (boilerplate) UI, I get this error repeatedly from the catch error handler. Same happens in graphQL playground. Really struggling to understand what's happening and why the resolver is getting spammed and generating this error.
RangeError: Maximum call stack size exceeded
at isLeafType (.../poc/node_modules/graphql/type/definition.js:247:20)
at coerceInputValueImpl (.../poc/node_modules/graphql/utilities/coerceInputValue.js:122:34)
Why is this happening, how to fix? Am I missing something obvious?
That's because both context.db and context.query internally still use the GraphQL API for CRUD. And since your custom mutation updateDog also has the same name as the generated mutation from schema updateDog, both the mutations are repeatedly invoking each other and hence the error RangeError: Maximum call stack size exceeded.
You can solve your problem in one of the two ways —
Change the name of your custom mutation to something else. Eg. updateDogCustom
or
(Practice caution) Instead of context.db.Dog.updateOne, use the prisma client to skip keystone's data layer and CRUD the database directly. Be warned, this means if you have hooks, access control or validation logic in place they won't be invoked.
export const extendGraphqlSchema = graphQLSchemaExtension<Context>({
typeDefs: `
type Mutation {
""" update a dog """
updateDog(
where: DogWhereUniqueInput!
data: DogUpdateInput!
): Dog
""" update a dog custom """
updateDogCustom(
where: DogWhereUniqueInput!
data: DogUpdateInput!
): Dog
}
`,
resolvers: {
Mutation: {
updateDog: async (root, { where: { id }, data: { name } }, context) => {
try {
const response = await context.prisma.dog.update({
where: { id },
data: { name },
});
return response;
} catch (updateError: any) {
throw updateError;
}
},
updateDogCustom: async (
root,
{ where: { id }, data: { name } },
context
) => {
try {
const response = await context.db.Dog.updateOne({
where: { id },
data: { name },
});
return response;
} catch (updateError: any) {
throw updateError;
}
},
},
},
});
Codesandbox here — https://codesandbox.io/s/winter-shadow-fz689e?file=/src/custom-schema.ts
You can run the graphql playground right from codesandbox from /api/graphql path. Eg. https://fz689e.sse.codesandbox.io/api/graphql
I just installed a new Next.js app. It has the following page:
// /pages/articles/[slug].js
import React from 'react'
import { useRouter } from 'next/router'
import ErrorPage from 'next/error'
const Article = (props) => {
const router = useRouter()
if (router.isFallback) {
return <div>Loading..</div>
}
if (!props['data']) {
return <ErrorPage statusCode={404} />
}
return (
<div>
Article content
</div>
)
}
export default Article
export const getStaticProps = async(context) => {
const slug = context.params.slug
const res = ["a", "b", "c"].includes(slug)
? {
props: {
data: slug
}
}
: {
props: {},
notFound: true
}
return res
}
export const getStaticPaths = async () => {
return {
paths: [
{ params: { slug: "a" }},
{ params: { slug: "b" }},
{ params: { slug: "c" }}
],
fallback: true
}
}
When the browser navigates to a non-existing page (e.g. http://localhost:3000/articles/d) then it returns the default nextjs 404 page, as expected.
But the browser network tab shows status 200 for the main document (the 404 error page). The only things in the network tab with status 404 are d.json and 404.js.
I think that the main document should also have 404 status. The getStaticProps docs say about the return value:
notFound - An optional boolean value to allow the page to return a 404 status and page
But in this case the page status is 200 and not 404. Is there something else necessary to do to return status 404?
Without fallback the status is 404.
For this specific use case you have to use fallback: 'blocking' instead.
export const getStaticPaths = async () => {
return {
paths: [
{ params: { slug: "a" }},
{ params: { slug: "b" }},
{ params: { slug: "c" }}
],
fallback: 'blocking'
}
}
Unlike fallback: true, it will not serve a "fallback" version if the page has not been generated yet. That's why you get the 200 status code currently.
Instead, fallback: 'blocking' will wait for the HTML to be generated before rendering the page - similar to what happens during server-side rendering. This means that if notFound: true is returned from getStaticProps you will get the proper 404 status code for the page request.
The title above is the error message, and below is the code.
I ran into this while following a tutorial on Gatsby. I wonder if anybody knows what happened and can give me some idea on what happened. I have little idea on what is going on . sorry......
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../components/layout'
import { parseImageUrl } from '#conradlin/notabase/src/utils'
export default ({ data }) => {
const { posts: { title, tags, publish_date, html, url, slug, desc, color, cover_image } } = data
return (
<Layout>
<div id = "main">
<div>{tags && tags.join(', ')}</div>
<h1>{title}</h1>
<div dangerouslySetInnerHTML={{ __html: html }} />
</div>
</Layout>
)
}
export const query = graphql`
query($slug: String!) {
posts(slug: { eq: $slug }) {
html
title
tags
publish_date{
startDate(formatString: "YYYY-MMM-DD", fromNow: false)
}
url
desc
color
cover_image
}
}
The tutorial is here for reference
https://conradlin.com/blog/posts/host-gatsbyjs-blog-with-notion-cms-and-netlify-for-free
This should be the query code
const path = require(`path`)
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const blogPost = await graphql(`
query {
allPosts(filter: {status: {eq: "published"}, content_type: {eq: "article"}}) {
nodes {
slug
url
}
}
}
`).then(result => {
if (result.errors) {
Promise.reject(result.errors);
}
result.data.allPosts.nodes.forEach(({ slug, url }) => {
createPage({
path: `blog/posts/${url}`,
component: path.resolve(`./src/templates/blogPost.js`),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: slug,
},
});
});
});
const newsPost = await graphql(`
query {
allPosts(filter: {status: {eq: "published"}, content_type: {eq: "newsletter"}}) {
nodes {
slug
url
}
}
}
`).then(result => {
if (result.errors) {
Promise.reject(result.errors);
}
result.data.allPosts.nodes.forEach(({ slug, url }) => {
createPage({
path: `subscribe/posts/${url}`,
component: path.resolve(`./src/templates/blogPost.js`),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: slug,
},
});
});
});
return Promise.all([blogPost, newsPost]);
};
If you look at:
result.data.allPosts.nodes.forEach(({ slug, url }) => {
createPage({
path: `subscribe/posts/${url}`,
component: path.resolve(`./src/templates/blogPost.js`),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: slug,
},
});
This snippet refers to the gatsby-node.js, where you create pages dynamically based on a GraphQL query for allPosts. In this case, you are creating each post that will have the following URL:
path: `subscribe/posts/${url}`,
However, to filter the data for each particular post, you need to pass to the template (set with component: path.resolve('./src/templates/blogPost.js')) you need to pass an identifier field, typically the id, the slug or some other unique value. That flow of data is done due to the context, where you tell what parameter is passed to the component template, the slug in this case:
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: slug,
},
Thereby, in you template you can do:
export const query = graphql`
query($slug: String!) {
posts(slug: { eq: $slug }) {
html
title
tags
publish_date{
startDate(formatString: "YYYY-MMM-DD", fromNow: false)
}
url
desc
color
cover_image
}
}
Notice the ($slug: String!) the exclamation mark (!) means that the field is non-nullable for the GraphQL query, or in other words, that is required. Your code is breaking because somewhere in your GraphQL query (in your gatsby-node.js) you are passing an empty/null value to that template and the query breaks.
Debug what's happening in that query or remove the non-nullable parameter by:
query($slug: String)
However, that will bypass the code-breaking but the issue will persist since you are providing an empty value.
I am learning Nextjs framework and I am having difficulty with dynamic route and how to fetch the data from an array. I followed the basics of Nextjs but this got me stuck.
What I want?
Get the information of this array which is stored as a items.tsx file:
export const projects = [
{
id: '2',
title: 'Italian recipes book',
description: `This a web design and soon-to-be a fully functional and responsive React application`
},
{
id: '1',
title: 'Chat App website',
description: `This is a homepage design and build for a concept project – a chat application. I designed the page
first then built a responsive web page using Webflow.`
},
]
As per nextjs website I was able to fetch the id and created a function to get the project data like so
import { projects } from "../models/projects";
export function getAllItemsIds() {
return projects.map(project => {
return {
params: {
id: project.id,
}
}
})
}
export function getProjectData(id) {
return {
id,
}
}
I believe the function getProjectData(id) is missing the rest of the data and I'm having problems on how to fetch it. I want to show the info of the array in my [id].tsx file according to the parameter (in this case, to the ID 1 or 2).
This is my [id].tsx file:
export async function getStaticPaths() {
const paths = getAllItemsIds()
return {
paths,
fallback: false
}
}
export async function getStaticProps({ params }) {
const projectData = getProjectData(params.id)
return {
props: {
projectData
}
}
}
const Project = ({ projectData }) => {
return (
<>
<div>
{projectData.id}
{projectData.title}
{projectData.description}
</div>
</>
)
}
export default Project;
As of now I am only able to display my projectData.id and I got a bit confused with all the getStaticPaths() and getStaticProps() thing. I'd really appreciate the help.
I don't know if that's the right way to do it, but I managed to do what I wanted like so:
added the other parameters and mapped the array inside the getProjectData
export function getProjectData(id, title, description ) {
projects.map(data => {
if (data.id === id) {
title = data.title;
description = data.description;
}
});
return {
id,
title,
description
}
}
and in the [id].tsx I updates the getStaticProps like so
export async function getStaticProps({ params, title, description }) {
const projectData = getProjectData(params.id, title, description)
return {
props: {
projectData
}
}
}
It did fetch the data according to the id parameter!
I'm constructing a GraphQL query using vue-apollo and graphql-tag.
If I hardcode the ID I want, it works, but I'd like to pass the current route ID to Vue Apollo as a variable.
Does work (hardcoded ID):
apollo: {
Property: {
query: PropertyQuery,
loadingKey: 'loading',
variables: {
id: 'my-long-id-example'
}
}
}
However, I'm unable to do this:
Doesn't work (trying to access this.$route for the ID):
apollo: {
Property: {
query: PropertyQuery,
loadingKey: 'loading',
variables: {
id: this.$route.params.id
}
}
}
I get the error:
Uncaught TypeError: Cannot read property 'params' of undefined
Is there any way to do this?
EDIT: Full script block to make it easier to see what's going on:
<script>
import gql from 'graphql-tag'
const PropertyQuery = gql`
query Property($id: ID!) {
Property(id: $id) {
id
slug
title
description
price
area
available
image
createdAt
user {
id
firstName
lastName
}
}
}
`
export default {
name: 'Property',
data () {
return {
title: 'Property',
property: {}
}
},
apollo: {
Property: {
query: PropertyQuery,
loadingKey: 'loading',
variables: {
id: this.$route.params.id // Error here!
}
}
}
}
</script>
You can't have access to "this" object like that:
variables: {
id: this.$route.params.id // Error here!
}
But you can like this:
variables () {
return {
id: this.$route.params.id // Works here!
}
}
Readimg the documentation( see Reactive parameters section) of vue-apollo you can use vue reactive properties by using this.propertyName. So just initialize the route params to a data property as then use it in you apollo object like this
export default {
name: 'Property',
data () {
return {
title: 'Property',
property: {},
routeParam: this.$route.params.id
}
},
apollo: {
Property: {
query: PropertyQuery,
loadingKey: 'loading',
// Reactive parameters
variables() {
return{
id: this.routeParam
}
}
}
}
}
While the accepted answer is correct for the poster's example, it's more complex than necessary if you're using simple queries.
In this case, this is not the component instance, so you can't access this.$route
apollo: {
Property: gql`{object(id: ${this.$route.params.id}){prop1, prop2}}`
}
However, you can simply replace it with a function, and it will work as you might expect.
apollo: {
Property () {
return gql`{object(id: ${this.$route.params.id}){prop1, prop2}}`
}
}
No need for setting extra props.