Why do my Astro builds fail when featuredImage is null? - wordpress

I'm using WPGraphQL to query all of my posts from WordPress. And I'm using Astro to display a list of cards for each of those posts on the front-end.
Here is what that my GraphQL query looks like:
/* BASIC FETCH API */
async function fetchAPI(query, { variables } = {}) {
const headers = { "Content-Type": "application/json" };
const res = await fetch(API_URL, {
method: "POST",
headers,
body: JSON.stringify({ query, variables }),
});
const json = await res.json();
if (json.errors) {
console.log(json.errors);
throw new Error("Failed to fetch API");
}
return json.data;
}
/* ALL POSTS QUERY */
export async function getAllPosts() {
const data = await fetchAPI(`
{
posts(first: 10000) {
edges {
node {
id
title
content
excerpt
slug
categories(first: 2) {
nodes {
slug
name
}
}
featuredImage {
node {
sourceUrl
}
}
}
}
}
}
`);
return data?.posts;
}
And here is how I am rendering those posts on my blog page:
<Section>
<Container>
<div class="post--card--wrapper">
{page.data.map((post) => (
<PostCard
src={post.node.featuredImage.node.sourceUrl}
href={`/posts/${post.node.slug}`}
title={post.node.title}
excerpt={`${post.node.excerpt.slice(0, 120)}...`}
/>
))}
</div>
<div class="pagination--wrapper py-6">
{page.url.prev ? (
<a href={page.url.prev || "#"} class="pagination--previous">
← Previous
</a>
) : null}
{page.url.next ? (
<a href={page.url.next || "#"} class="pagination--next">
Next →
</a>
) : null}
</div>
</Container>
</Section>
And this is the code for the PostCard.astro component:
---
export interface Props {
href?: string;
src?: string;
alt?: string;
title?: string;
categories?: string;
excerpt?: string;
}
const { href, src, alt, title, categories, excerpt } = Astro.props;
---
<a href={href} class="post--card">
{src && <img src={src} alt={alt} class="post--thumbnail" />}
<div class="post--card--bottom">
<h5 class="post--card--heading">{title}</h5>
<div class="post--card--excerpt" set:html={excerpt}></div>
</div>
</a>
The problem is that a few of the posts do not have featured images set. And so, my builds are failing with the following error message:
"TypeError: Cannot read properties of null (reading 'node')"
I basically want to tell GraphQL to grab the featuredImage field for each post if it exists. But if featuredImage does not exist, keep going and get the rest of them.

Conditionally render elements in Astro
The answer to this in Astro is to conditionally render an element. This is a known pattern
all you have to do is add post.node.featuredImage && in front of the element to render conditionally
<div class="post--card--wrapper">
{page.data.map((post) => (
{post.node.featuredImage &&
<PostCard
...
/>
}
))}
</div>
{featuredImage &&
}
reference in Astro docs : https://docs.astro.build/en/tutorial/2-pages/3/#conditionally-render-elements

Related

Nextjs Build fail on Vercel

I'm trying to deploy my NextJs app (using GraphCMS) on Vercel. When I build the app on my computer it works fine, I can build and run the app locally but once I try to deploy the same exact app on Vercel it crash with this error
TypeError: Cannot read properties of undefined (reading 'document')
at Object.parseRequestExtendedArgs (/vercel/path0/node_modules/graphql-request/dist/parseArgs.js:37:25)
at /vercel/path0/node_modules/graphql-request/dist/index.js:422:42
at step (/vercel/path0/node_modules/graphql-request/dist/index.js:63:23)
at Object.next (/vercel/path0/node_modules/graphql-request/dist/index.js:44:53)
at /vercel/path0/node_modules/graphql-request/dist/index.js:38:71
at new Promise ()
at __awaiter (/vercel/path0/node_modules/graphql-request/dist/index.js:34:12)
at request (/vercel/path0/node_modules/graphql-request/dist/index.js:418:12)
at getPosts (/vercel/path0/.next/server/chunks/104.js:1143:82)
at getStaticPaths (/vercel/path0/.next/server/pages/post/[slug].js:98:86)
Build error occurred
Error: Failed to collect page data for /post/[slug]
at /vercel/path0/node_modules/next/dist/build/utils.js:959:15
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
type: 'Error'
}
error Command failed with exit code 1.
I don't understand where this is coming from.
pages/post/[slug].js
import React from "react";
import { useRouter } from "next/router";
import {
PostDetail,
Categories,
PostWidget,
Author,
Comments,
CommentsForm,
Loader,
} from "../../components";
import { getPosts, getPostDetails } from "../../services";
import { AdjacentPosts } from "../../sections";
const PostDetails = ({ post }) => {
const router = useRouter();
if (router.isFallback) {
return <Loader />;
}
return (
<>
<div className="container mx-auto px-10 mb-8">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
<div className="col-span-1 lg:col-span-8">
<PostDetail post={post} />
<Author author={post.author} />
<AdjacentPosts slug={post.slug} createdAt={post.createdAt} />
<CommentsForm slug={post.slug} />
<Comments slug={post.slug} />
</div>
<div className="col-span-1 lg:col-span-4">
<div className="relative lg:sticky top-8">
<PostWidget
slug={post.slug}
categories={post.categories.map((category) => category.slug)}
/>
<Categories />
</div>
</div>
</div>
</div>
</>
);
};
export default PostDetails;
// Fetch data at build time
export async function getStaticProps({ params }) {
const data = await getPostDetails(params.slug);
return {
props: {
post: data,
},
};
}
// Specify dynamic routes to pre-render pages based on data.
// The HTML is generated at build time and will be reused on each request.
export async function getStaticPaths() {
const posts = await getPosts();
return {
paths: posts.map(({ node: { slug } }) => ({ params: { slug } })),
fallback: false,
};
}
here is the Graphql query getPosts
export const getPosts = async () => {
const query = gql`
query MyQuery {
postsConnection {
edges {
cursor
node {
author {
bio
name
id
photo {
url
}
}
createdAt
slug
title
excerpt
displayedDate
featuredImage {
url
}
categories {
name
slug
}
}
}
}
}
`;
const result = await request(graphqlAPI, query);
return result.postsConnection.edges;
};
getPostDetails
export const getPostDetails = async (slug) => {
const query = gql`
query GetPostDetails($slug: String!) {
post(where: { slug: $slug }) {
title
excerpt
featuredImage {
url
id
}
author {
name
bio
photo {
url
}
}
createdAt
slug
content {
raw
}
categories {
name
slug
}
displayedDate
}
}
`;
const result = await request(graphqlAPI, query, { slug });
return result.post;
};
I really don't understand why I can build it locally but not en Vercel, Thanks
Tried to modify queries, turn off fallback and others things that did not work

Sanity - product data not displaying in dynamic (slug) file for individual products

I am able to list the products on the main products page, but clicking on a product to go to that product's page will throw an error:
TypeError: Cannot read properties of undefined (reading 'title')
Here is the code for my [slug].js file:
export default function Product({ products }) {
return (
<>
<h1>{products.title}</h1>
<p>{products.price}</p>
<hr />
{/* <img src={urlFor(images[0]).width(100).url()} /> */}
<PortableText value={products.body} components={ptComponents} />
<Link href="/">
<a>Back home</a>
</Link>
</>
);
}
const query = groq`*[_type == "product" && slug.current == $slug][0]{
_id,
title,
image,
price,
vendor,
blurb,
"slug": slug.current,
"categories": category[]->{title, slug},
mainImage,
body,
}`;
export async function getStaticPaths() {
const paths = await client.fetch(
groq`*[_type == "product" && defined(slug.current)][].slug.current`
);
return {
paths: paths.map((slug) => ({ params: { slug } })),
fallback: true,
};
}
export async function getStaticProps({ params }) {
// const { slug = "" } = context.params;
const products = await client.fetch(query, {
products: params.product,
});
return {
props: {
products,
},
};
}

Apollo GraphQL loading query

I am using Apollo GraphQL to fetch posts to populate a next js project.
The code I currently have works but I would like to implement the loading state of useQuery instead of what I have below.
This is the index (Posts page if you will)
import Layout from "../components/Layout";
import Post from "../components/Post";
import client from "./../components/ApolloClient";
import gql from "graphql-tag";
const POSTS_QUERY = gql`
query {
posts {
nodes {
title
slug
postId
featuredImage {
sourceUrl
}
}
}
}
`;
const Index = props => {
const { posts } = props;
return (
<Layout>
<div className="container">
<div className="grid">
{posts.length
? posts.map(post => <Post key={post.postId} post={post} />)
: ""}
</div>
</div>
</Layout>
);
};
Index.getInitialProps = async () => {
const result = await client.query({ query: POSTS_QUERY });
return {
posts: result.data.posts.nodes
};
};
export default Index;
Then it fetches an individual Post
import Layout from "../components/Layout";
import { withRouter } from "next/router";
import client from "../components/ApolloClient";
import POST_BY_ID_QUERY from "../queries/post-by-id";
const Post = withRouter(props => {
const { post } = props;
return (
<Layout>
{post ? (
<div className="container">
<div className="post">
<div className="media">
<img src={post.featuredImage.sourceUrl} alt={post.title} />
</div>
<h2>{post.title}</h2>
</div>
</div>
) : (
""
)}
</Layout>
);
});
Post.getInitialProps = async function(context) {
let {
query: { slug }
} = context;
const id = slug ? parseInt(slug.split("-").pop()) : context.query.id;
const res = await client.query({
query: POST_BY_ID_QUERY,
variables: { id }
});
return {
post: res.data.post
};
};
export default Post;
Whenever I have tried to use the useQuery with '#apollo/react-hooks' I always end up with a data.posts.map is not a function.
When you use useQuery, you should not destructure the data like result.data.posts.nodes because the data field will be undefined and loading is true for the first time calling. That why you got an error data.posts.map is not a function. Then if your query fetch data successfully, the loading field will be false.
You can try it:
import Layout from "../components/Layout";
import Post from "../components/Post";
import client from "./../components/ApolloClient";
import { useQuery } from "#apollo/react-hooks"
import gql from "graphql-tag";
const POSTS_QUERY = gql`
query {
posts {
nodes {
title
slug
postId
featuredImage {
sourceUrl
}
}
}
}
`;
const Index = props => {
const { posts } = props;
const { loading, error, data } = useQuery(POSTS_QUERY);
if (loading) return <div>Loading...</div>
if (error) return <div>Error</div>
return (
<Layout>
<div className="container">
<div className="grid">
{data.posts.nodes.length
? data.posts.nodes.map(post => <Post key={post.postId} post={post} />)
: ""}
</div>
</div>
</Layout>
);
};

how to display spinner when data is being fetched from cloud firestore in vuejs?

i'm working on firebase and vuejs with vuex as well. so in onauthStateChanged() method i try to get all the data form posts collection. its takes some time to display, In meanwhile i want to display spinner that specifies the user where some something is being loading.
i tried and its works cool, but the problem with code is
<loadingSpinner v-if="loading"></loadingSpinner>
<div v-if="posts.length">
<div v-for="post in posts" v-bind:key=post.id class="post">
<h5>{{ post.userName }}</h5>
<span>{{ post.createdOn | formatDate }}</span>
<p>{{ post.content | trimLength }}</p>
<ul>
<li><a #click="openCommentModal(post)">comments {{ post.comments }}</a></li>
<li><a #click="likePost(post.id, post.likes)">likes {{ post.likes }}</a></li>
<li><a #click="viewPost(post)">view full post</a></li>
</ul>
</div>
</div>
<div v-else>
<p class="no-results">There are currently no posts</p>
</div>
Spinner component responsible for spin animation:
<loadingSpinner v-if="loading"></loadingSpinner>
And the below html code is for displaying data from firebase
Where posts and loading variables are the computed properties from vuex state
problem is when is reload the page, spinner showing along the
<div v-else>
<p class="no-results">There are currently no posts</p>
</div>
I want to restrict the v-else condition when the spinner is being loaded.
By the way, the loading computed properties is a boolean that reacts based on onAuthstateChanged() firebase method
this is my entire vuex store file :
import Vue from 'vue'
import Vuex from 'vuex'
const fb = require('./firebaseConfig.js')
Vue.use(Vuex)
// handle page reload
fb.auth.onAuthStateChanged(user => {
if (user) {
store.commit('setCurrentUser', user)
store.dispatch('fetchUserProfile')
fb.usersCollection.doc(user.uid).onSnapshot(doc => {
store.commit('setUserProfile', doc.data())
})
// realtime updates from our posts collection
fb.postsCollection.orderBy('createdOn', 'desc').onSnapshot(querySnapshot => {
// check if created by currentUser
let createdByCurrentUser
if (querySnapshot.docs.length) {
createdByCurrentUser = store.state.currentUser.uid == querySnapshot.docChanges[0].doc.data().userId ? true : false
}
// add new posts to hiddenPosts array after initial load
if (querySnapshot.docChanges.length !== querySnapshot.docs.length
&& querySnapshot.docChanges[0].type == 'added' && !createdByCurrentUser) {
let post = querySnapshot.docChanges[0].doc.data()
post.id = querySnapshot.docChanges[0].doc.id
store.commit('setHiddenPosts', post)
} else {
store.commit('setLoading', true)
let postsArray = []
querySnapshot.forEach(doc => {
let post = doc.data()
post.id = doc.id
postsArray.push(post)
})
store.commit('setPosts', postsArray)
store.commit('setLoading', false)
}
})
}
})
export const store = new Vuex.Store({
state: {
currentUser: null,
userProfile: {},
posts: [],
hiddenPosts: [],
loading: true
},
actions: {
clearData({ commit }) {
commit('setCurrentUser', null)
commit('setUserProfile', {})
commit('setPosts', null)
commit('setHiddenPosts', null)
},
fetchUserProfile({ commit, state }) {
fb.usersCollection.doc(state.currentUser.uid).get().then(res => {
commit('setUserProfile', res.data())
}).catch(err => {
console.log(err)
})
},
updateProfile({ commit, state }, data) {
let name = data.name
let title = data.title
fb.usersCollection.doc(state.currentUser.uid).update({ name, title }).then(user => {
// update all posts by user to reflect new name
fb.postsCollection.where('userId', '==', state.currentUser.uid).get().then(docs => {
docs.forEach(doc => {
fb.postsCollection.doc(doc.id).update({
userName: name
})
})
})
// update all comments by user to reflect new name
fb.commentsCollection.where('userId', '==', state.currentUser.uid).get().then(docs => {
docs.forEach(doc => {
fb.commentsCollection.doc(doc.id).update({
userName: name
})
})
})
}).catch(err => {
console.log(err)
})
}
},
mutations: {
setLoading(state, payload){
state.loading = payload
},
setCurrentUser(state, val) {
state.currentUser = val
// console.log(val)
},
setUserProfile(state, val) {
state.userProfile = val
// console.log(val)
},
setPosts(state, val) {
if (val) {
state.posts = val
} else {
state.posts = []
}
},
setHiddenPosts(state, val) {
if (val) {
// make sure not to add duplicates
if (!state.hiddenPosts.some(x => x.id === val.id)) {
state.hiddenPosts.unshift(val)
}
} else {
state.hiddenPosts = []
}
}
},
})
any suggestions?
I would tweak your v-if/v-else logic at bit.
<loadingSpinner v-if="loading" />
<div v-else-if="posts.length"></div>
<div v-else>
<p class="no-results">There are currently no posts</p>
</div>
The difference is v-else-if on posts.length, instead of v-if. This way, there are 3 distinct states.
Loading, show spinner.
Not loading, show posts.
Not loading, there are no posts, show no results.

React props using Meteor Apollo

I am playing with the Meteor Apollo demo repo.
I am having difficulty passing variables down to children with React. I am getting an error
imports/ui/Container.jsx:10:6: Unexpected token (10:6)
The below code is the Container.jsx component:
import React from 'react';
import { Accounts } from 'meteor/std:accounts-ui';
class Container extends React.Component {
render() {
let userId = this.props.userId;
let currentUser = this.props.currentUser;
}
return (
<Accounts.ui.LoginForm />
{ userId ? (
<div>
<pre>{JSON.stringify(currentUser, null, 2)}</pre>
<button onClick={() => currentUser.refetch()}>Refetch!</button>
</div>
) : 'Please log in!' }
);
}
}
It is passed props via the Meteor Apollo data system (I have omitted some imports at the top):
const App = ({ userId, currentUser }) => {
return (
<div>
<Sidebar />
<Header />
<Container userId={userId} currentUser={currentUser} />
</div>
)
}
// This container brings in Apollo GraphQL data
const AppWithData = connect({
mapQueriesToProps({ ownProps }) {
if (ownProps.userId) {
return {
currentUser: {
query: `
query getUserData ($id: String!) {
user(id: $id) {
emails {
address
verified
}
randomString
}
}
`,
variables: {
id: ownProps.userId,
},
},
};
}
},
})(App);
// This container brings in Tracker-enabled Meteor data
const AppWithUserId = createContainer(() => {
return {
userId: Meteor.userId(),
};
}, AppWithData);
export default AppWithUserId;
I would really appreciate some pointers.
I believe the error is that you accidentally ended the render function before the return statement.
render() { // <- here it starts
let userId = this.props.userId;
let currentUser = this.props.currentUser;
} // <- here it ends
Another error is that your return statement doesn't return a single DOM element, but two of them: an Accounts.ui.LoginForm and a div. The return function should only return one element. Just put the entire thing into a single <div>.

Resources