Server Error, error serializing `getServerSideProps` - javascript

My console shows me that there is a problem with serving, which I can not solve:
Uncaught Error: Error serializing .title returned from getServerSideProps in "/property/[slug]".
Reason: undefined cannot be serialized as JSON. Please use null or omit this value.
I am using sanity and next.js
This is my code from the slug component:
export const getServerSideProps = async (pageContext) => {
const pageSlug = pageContext.query.slug
const query = `*[-type == "property" && slug.current == $pageSlug][0]{
title,
location,
propertyType,
mainImage,
images,
pricePerNight,
beds,
bedrooms,
description,
host->{
_id,
name,
slug,
image
},
reviews[]{
...,
traveller->{
_id,
name,
slug,
image
}
}
}`
const property = await sanityClient.fetch(query, { pageSlug})
if (!property) {
return {
props: null,
notFound: true,
}
} else {
return {
props: {
title: property.title,
location: property.location,
propertyType: property.propertyType,
mainImage: property.mainImage,
images: property.images,
pricePerNight: property.pricePerNight,
beds: property.beds,
bedrooms: property.bedrooms,
description: property.description,
host: property.host,
reviews: property.reviews
}
}
}
}
export default Property
In my terminal, the client and server compiled successfully.
I tried to return JSON.stringify but still had no success.

According to Next.js documentation, props object should be serializable to be fetched correctly by your rendered site. undefined values (just like functions) are not serializable.
You can provide null fallback values for props which can be undefined:
export const getServerSideProps = () => {
// some fetching logic
return {
props: {
title: property.title || null
// rest of props
}
};
}

Related

Keystone 6 custom schema - mutation not working - Maximum call stack size exceeded

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

Import image to a component from React project

I'm building an app with React and Typescript.
I'm retrieving data using a free API. I'm trying to use a default image for the objects that do not have them.
This is the structure of my project:
In movies.service.tsx I fetch the data and assign to the property picture of movie object the value retrieved from the DB (in case there is no value, I would like to assign it the path of default-poster.png):
import * as img from '../images/default-poster.png';
console.log('img ', img);
const movieApiBaseUrl = "https://api.themoviedb.org/3";
const posterBaseUrl = "https://image.tmdb.org/t/p/w300";
export interface Movie {
id: number;
title: string;
rating: number;
description: string;
picture?: string;
date: string;
}
export function fetchMovies(): Promise<Movie[]> {
return fetch(
`${movieApiBaseUrl}/discover/movie?sort_by=year.desc&api_key=${process.env.REACT_APP_API_KEY}`
)
.then((res) => res.json())
.then((res) => mapResult(res.results))
.catch(() => {
return [];
});
}
// = movie has to be after const {} here
function mapResult(res: any[]): Movie[] {
return res.map((movie) => {
const {
id,
title,
vote_average,
overview,
poster_path,
date,
} = movie;
return {
id: id,
title: title,
rating: vote_average,
description: overview,
picture: poster_path ? `${posterBaseUrl}${poster_path}` : img,
date: date,
};
});
}
However, I was not allowed to assign img to picture property by Typescript. I have also consoled out img value and it is equal to a module:
Module default: "data:image/png;base64,iVBORw0KGgoAAAA <...>" Symbol(Symbol.toStringTag): "Module" esModule: true __proto: Object
I need to assign the paths of images to picture property so that I could use them in MovieCards.tsx component later on.
What should I change?
Thanks!
The solution is importing the default image to movies.service.tsx:
import noImage from '../images/no-image-available.png';
and then passing the variable noImage as the third operand of ternary operator:
picture: poster_path ? `${posterBaseUrl}${poster_path}` : noImage,

Cannot read property 'substring' of undefined NUXT

I am having problems in an html tag that uses an object from my object in store.
When i refresh my page, the array from my sotre is empty, so i when i refresh in the index page, it will first load the html, then the mounted method, and its where i fill my store. its says that Cannot read property 'substring' of undefined
index.vue that i use this object:
<p v-html="pegaPrimeiroPost.conteudo.substring(0,500)"></p>
export default of index.vue:
computed: {
...mapGetters({
postsDB: "postagensDB/pegaPosts",
pegaPrimeiroPost: "postagensDB/pegaPrimeiroPost",
}),
},
methods: {
...mapActions({
buscaPostDB: "postagensDB/pegaPostsDB",
}),
},
async mounted() {
**await this.buscaPostDB();**
});
},
this object pegaPrimeiroPost is an objetct from my array that i fill from my database.
store/postagensDB:
import axios from 'axios'
export const state = () => ({
posts: [],
primeiroPost: {},
})
export const getters = {
pegaPosts(state) {
return state.posts;
},
pegaPrimeiroPost(state) {
return state.primeiroPost;
},
}
export const actions = {
async pegaPostsDB(state) {
await axios
.get("http:/MY_API_ADRESS")
.then((response) => {
state.commit('carregaStatePosts', response.data)
})
.catch((response) => {
console.log(response)
});
},
}
export const mutations = {
async carregaStatePosts(state, postsDB) {
state.posts = postsDB.posts;
state.primeiroPost = state.posts[0];
},
}
If i erase the substring() method, reload the page, then it will fill my store; and then re-add the substring(), it works, but wont solve my prob. Can anyone help me?
I've never used Vue, but this sounds like you have some sort of async action that populates the object, but your default value (used before the async call has completed) doesn't have that property. The quickest solution is probably just give it a value if it doesn't exist:
(pegaPrimeiroPost.conteudo || '').substring(0,500)
or set a "default" object with that property:
export const state = () => ({
posts: [],
primeiroPost: { conteudo: '' },
})

error There was an error in your GraphQL query: Variable "$slug" of required type "String!" was not provided

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.

Possible to combine apollo queries (within nuxt?)

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>

Categories

Resources