I am trying to build sub-pages for a projects category in Gatsby, each project parent page already generates the way it should but the sub-pages do not.
Each project can have zero to many sub-pages, I only want a sub-page to be generated if it exists. Data is coming from a headless CMS through GraphQL
My loop for generating these pages in gatsby-node.js currently looks like this:
result.data.allSanityProjects.edges.forEach(({ node }) => {
node.projectChildPages.map(childPage => {
if (node && node.projectChildPages.length > 0 && node.projectChildPages.slug) {
createPage({
path: childPage.slug + "/" + node.projectChildPages.slug,
component: projectsSubPages,
context: {
slug: childPage.slug + "/" + node.projectChildPages.slug,
},
});
}
});
});
});
This loops through the "allSanityProjects" part of this GrapQL query
{
allSanityDefaultPage {
edges {
node {
slug
}
}
}
allSanityProjects {
edges {
node {
slug
projectChildPages {
slug
}
}
}
}
}
The results of running just the allSanityProjects-query looks like this
{
"data": {
"allSanityProjects": {
"edges": [
{
"node": {
"slug": "project-3",
"projectChildPages": []
}
},
{
"node": {
"slug": "project-1",
"projectChildPages": [
{
"slug": "project-1"
},
{
"slug": "Doggolicious"
},
{
"slug": "no-cats"
}
]
}
},
{
"node": {
"slug": "Project-2",
"projectChildPages": []
}
}
]
}
}
}
Gatsby fails building the project child pages with the following error.
warn The GraphQL query in the non-page component
Exported queries are only executed for Page components. It's possible you're
trying to create pages in your gatsby-node.js and that's failing for some
reason.
If the failing component(s) is a regular component and not intended to be a page
component, you generally want to use a <StaticQuery> (https://gatsbyjs.org/docs/static-query)
instead of exporting a page query.
If you're more experienced with GraphQL, you can also export GraphQL
fragments from components and compose the fragments in the Page component
query and pass data down into the child component — https://graphql.org/learn/queries/#fragments
My template looks like this:
import React from "react";
import { useStaticQuery, graphql } from "gatsby";
import Layout from "../components/layout";
const BlockContent = require("#sanity/block-content-to-react");
const projectsSubPages = ({ data }) => {
const pageData = data.sanityProjects.projectChildPages;
return (
<Layout>
<BlockContent blocks={pageData._rawBlockContent} />
</Layout>
);
};
export const query = graphql`
query($slug: String!) {
sanityProjects(slug: { eq: $slug }) {
projectChildPages {
_rawBlockContent
slug
title
}
}
}
`;
export default projectsSubPages;
As far as I can tell my error is in my gatsby-node.js file, not in my template even though gatsby tells me my error is in my template. I've tried running the exact same templates as the others I use (just with different queries in them) and still get the same error.
My full gatsby-node.js file:
exports.createPages = ({ actions, graphql }) => {
const path = require(`path`);
const { createPage } = actions;
const projects = path.resolve("src/templates/projects.js");
const defaultPage = path.resolve("src/templates/defaultPage.js");
const projectsSubPages = path.resolve("src/templates/projectsSubPages.js");
return graphql(`
{
allSanityDefaultPage {
edges {
node {
slug
}
}
}
allSanityProjects {
edges {
node {
slug
projectChildPages {
slug
}
}
}
}
}
`).then((result) => {
if (result.errors) {
reporter.panic("failed to create pages ", result.errors);
}
result.data.allSanityDefaultPage.edges.forEach(({ node }) => {
createPage({
path: node.slug,
component: defaultPage,
context: {
slug: node.slug,
},
});
});
result.data.allSanityProjects.edges.forEach(({ node }) => {
createPage({
path: node.slug,
component: projects,
context: {
slug: node.slug,
},
});
});
result.data.allSanityProjects.edges.forEach(({ node }) => {
node.projectChildPages.map(childPage => {
if (node && node.projectChildPages.length > 0 && node.projectChildPages.slug) {
createPage({
path: childPage.slug + "/" + node.projectChildPages.slug,
component: projectsSubPages,
context: {
slug: childPage.slug + "/" + node.projectChildPages.slug,
},
});
}
});
});
});
};
code
result.data.allSanityProjects.edges.forEach(({ node }) => {
node.projectChildPages.map(childPage => {
if (node && node.projectChildPages.length > 0 && node.projectChildPages.slug) {
Condition doesn't have much sense there:
if you're in .map() then for sure node and node.projectChildPages.length > 0 are true
projectChildPages is an array so no projectChildPages.slug here
query
Your fetched data (source) doesn't contain _rawBlockContent so you can't query for this in page component.
Related
I am integrating Vite SSR to existing vue project. I copied vite configuration from SSR playground project and bumped ionic version to 6 because of dynamic loading issue from stencil.
After upgrading, it isn't compiling, showing this error.
12:03:15 AM [vite] Error when evaluating SSR module /src/components/ImportType.vue:
/data/Work/ssr-vue/node_modules/#ionic/core/components/ion-accordion.js:4
import { proxyCustomElement, HTMLElement, h, Host } from '#stencil/core/internal/client';
^^^^^^
SyntaxError: Cannot use import statement outside a module
Here is my vite.config.js file
const vuePlugin = require('#vitejs/plugin-vue')
const vueJsx = require('#vitejs/plugin-vue-jsx')
const virtualFile = '#virtual-file'
const virtualId = '\0' + virtualFile
const nestedVirtualFile = '#nested-virtual-file'
const nestedVirtualId = '\0' + nestedVirtualFile
/**
* #type {import('vite').UserConfig}
*/
module.exports = {
plugins: [
vuePlugin(),
vueJsx(),
{
name: 'virtual',
resolveId(id) {
if (id === '#foo') {
return id
}
},
load(id) {
if (id === '#foo') {
return `export default { msg: 'hi' }`
}
}
},
{
name: 'virtual-module',
resolveId(id) {
if (id === virtualFile) {
return virtualId
} else if (id === nestedVirtualFile) {
return nestedVirtualId
}
},
load(id) {
if (id === virtualId) {
return `export { msg } from "#nested-virtual-file";`
} else if (id === nestedVirtualId) {
return `export const msg = "[success] from conventional virtual file"`
}
}
}
],
ssr: {
external: ["npm: #ionic/vue"]
},
build: {
minify: false
}
}
Please help me.
I have encountered a problem while making a personal Gatsby site whereby blog post files are having thier unique folders included in the slug.
For example a file structure of:
data
|
|– blog
|
|– blog-1
| |
| |-blog-1.mdx
| |-img.jpg
|
|– blog-2
| |
| |-blog-2.mdx
| |-img.jpg
|
|– blog-3
| |
| |-blog-3.mdx
| |-img.jpg
Will, for example, produce slugs like this
{
"data": {
"allMdx": {
"edges": [
{
"node": {
"fields": {
"slug": "/blog-1/blog-1/"
},
"frontmatter": {
"title": "Blog 1",
"posttype": "blog"
}
}
},
{
"node": {
"fields": {
"slug": "/blog-2/blog-2/"
},
"frontmatter": {
"title": "Blog 2",
"posttype": "blog"
}
}
},
{
"node": {
"fields": {
"slug": "/blog-3/blog-3/"
},
"frontmatter": {
"title": "Blog 3",
"posttype": "blog"
}
}
},
I expect them to produce a slug like this:
{
"node": {
"fields": {
"slug": "/blog-1/"
},
"frontmatter": {
"title": "Blog 1",
"posttype": "blog"
}
}
},
The path to the parent blog folder is included in my gatsby-config like this:
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/data/blog`,
name: `blog`,
},
},
And then my gatsby-node folder is set up like this:
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
const blogPost = path.resolve(`./src/templates/blog-post.js`)
const portfolioPost = path.resolve(`./src/templates/portfolio-post.js`)
const journeyPost = path.resolve(`./src/templates/journey-post.js`)
return graphql(
`
{
allMdx(
sort: { fields: [frontmatter___date], order: DESC }
limit: 1000
) {
edges {
node {
fields {
slug
}
frontmatter {
title
posttype
}
}
}
}
}
`
).then(result => {
if (result.errors) {
throw result.errors
}
const posts = result.data.allMdx.edges
posts.forEach((post, index) => {
const previous = index === posts.length - 1 ? null : posts[index + 1].node
const next = index === 0 ? null : posts[index - 1].node
if (post.node.frontmatter.posttype == "portfolio") {
createPage({
path: `/portfolio${post.node.fields.slug}`,
component: portfolioPost,
context: {
slug: post.node.fields.slug,
previous,
next,
},
})
} else if (post.node.frontmatter.posttype == "journey") {
createPage({
path: `/journey${post.node.fields.slug}`,
component: journeyPost,
context: {
slug: post.node.fields.slug,
previous,
next,
},
})
} else {
createPage({
path: `/blog${post.node.fields.slug}`,
component: blogPost,
context: {
slug: post.node.fields.slug,
previous,
next,
},
})
}
})
return null
})
}
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `Mdx`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
Note that the journey and portfolio paths in this file are at this point in time doing exactly the same thing as the blog path. They are set up in exactly the same way and are just split out depending on posttype. Pages are created fine but they are all using the unwanted folder/file.mdx slug.
Fixed by looking at other blog examples.
Post filename needs to be index.md or index.mdx
I am sure there is a better way but I was able to solve it by making changes in gatsby-node.js to only take the substring after the last slash (/) in from the file path. If someone knows a better way I will be glad to know that.
Old:
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
New:
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode, trailingSlash:false })
createNodeField({
name: `slug`,
node,
value: `${value.indexOf("/") > -1 ? value.substring(value.lastIndexOf("/")) : value}`,
})
}
}
trying to map images from a folder in a gatsby project. I set up the query in a way I thought would work based on guides I've seen. I do not get any errors and the site loads, but the images do not show up. Any thoughts? Thanks!
gatsby-config.js:
plugins: [
`gatsby-plugin-react-helmet`,
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
query:
const data = useStaticQuery(graphql`
query {
allFile(filter:{extension:{regex:"/social/"}}) {
edges {
node {
childImageSharp {
fixed(width: 150, height: 150) {
...GatsbyImageSharpFixed
}
}
}
}
}
}
`)
return:
<div className='socialPhotos'>
{data.allFile.edges.map(node =>
<Img
key={node.id}
title="Photo image"
alt="Photo"
fixed={node.childImageSharp.fixed}
/>
)}
</div>
The filter in your graphQL query might be the problem. Did you try your query in graphiQL in the browser?
Because you define images in your gatsby-config.js, I would suggest filtering with sourceInstanceName:
const data = useStaticQuery(graphql`
query {
allFile(filter: {sourceInstanceName: {eq: "images"}}) {
edges {
node {
childImageSharp {
fixed(width: 150, height: 150) {
...GatsbyImageSharpFixed
}
}
}
}
}
}
`)
Paste your query into GraphiQL in your browser and test if you get data back: http://localhost:8000/___graphql
EDIT
This is how you can get one image from your query inside your component
const YourComponent = (props) => {
const { data: { allFile: { edges } } } = props;
const oneImage = edges.filter(
el => el.node.childImageSharp.fixed.originalName === "yourImageFileName.png")
[0].node.childImageSharp.fixed;
// ...
I'm currently migrating an application I developed from redux to apollo. I'm following this example trying to implement apollo-link-state and apollo-cache-inmemory but I'm struggling to understand how their framework works. It would be great if someone could answer some questions, so here we go:
Use Case: Store modals information (basically wether it's open or not) in my cache memory
Here is my code:
// apollos.js
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { withClientState } from 'apollo-link-state';
import { HttpLink } from 'apollo-link-http';
import { ApolloLink } from 'apollo-link';
import { modalStateQuery } from "./common/queries/modal.query";
const httpLinkOptions = {
uri: 'http://localhost:8080/graphql',
};
const httpLink = new HttpLink(httpLinkOptions);
const cache = new InMemoryCache({
dataIdFromObject: o => o.id
});
const typeDefs = `
type Modal {
id: ID!
open: Boolean!
}
type Query {
modal(id: ID!): Modal
modals: [Modal]
}
`;
const defaults = {
modals: [
{
__typename: "Modal",
id: "login",
open: false
},
{
__typename: "Modal",
id: "signup",
open: false
}
]
};
const resolvers = {
Query: {
modal: (_, { id }, { cache }) => {
console.log("get modal");
try {
const data = cache.readQuery({ query: modalStateQuery.getOne, variables: { id } });
console.log("data", data);
} catch (e) {
console.log("error", e);
}
return null;
},
modals: (_, { }, { cache }) => {
console.log("Modal List Resolver"); // this is never logged
}
}
}
const stateLink = withClientState({
cache,
resolvers,
defaults,
typeDefs
});
const link = ApolloLink.from([stateLink, httpLink]);
const client = new ApolloClient({
link,
cache,
dataIdFromObject: o => o.id
});
export default client;
-
// modal.query.js
import gql from "graphql-tag";
export const modalStateQuery = {
getOne: gql`
query ModalState($id: String!) {
modal(id: $id) #client {
id
open
}
}`,
getAll: gql`
query {
modals #client {
id
open
}
}
`
};
-
// modal.js
// ...
// fetching both for test purposes
export default compose(
graphql(modalStateQuery.getOne, { name: "modal" }),
graphql(modalStateQuery.getAll, { name: "allModals" })
)(Modal);
Ok, now the questions:
The number one problem I'm having is with the modal($id: id) query. When I execute modalStateQuery.getAll the modals resolver is never called, but I still get the list I defined in defaults in my component. But when I execute modalStateQuery.getOne I always get the same error:
error Error: Can't find field modal({"id":"login"}) on object (ROOT_QUERY) {
"modals": [
{
"type": "id",
"generated": false,
"id": "login",
"typename": "Modal"
},
{
"type": "id",
"generated": false,
"id": "signup",
"typename": "Modal"
}
]
}.
at readStoreResolver (readFromStore.js:71)
at executeField (graphql.js:90)
at graphql.js:46
at Array.forEach (<anonymous>)
at executeSelectionSet (graphql.js:40)
at graphql (graphql.js:35)
at diffQueryAgainstStore (readFromStore.js:124)
at readQueryFromStore (readFromStore.js:37)
at InMemoryCache../node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.read (inMemoryCache.js:84)
at InMemoryCache../node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.readQuery (inMemoryCache.js:181)
What is this array it is showing to me? Why isn't there all the props I defined in defaults (like open: false)? Could it be something wrong with the way I create my defaults?
In the apollo-cache-inmemory docs it doesn't define any resolvers, it just says that you should query your data just like you are doing it in the backend, passing the variables.
Also, why does modalStateQuery.getAll works even though the resolver is never called? What if I do want that resolver to be called (Maybe I want to check my backend first to check permissions or smth)?
Another curious behavior I noticed: When executing the modal resolver, the id variable is always correct, even though I didn't explicitly pass it as variable in my component, but the Modal.js component does have an id props that I pass to it:
return <Modal id="login"><LoginForm /></Modal>;
It makes me believe that apollo already recognizes that the Modal.js is being called with an id prop and automatically passes it to the query as a variable. Is that correct? It does it for any variable?
Thanks!
I have the following Relay container in my Feed component, that it is a simple list of Posts
export default Relay.createContainer(Feed, {
initialVariables: {
count: 5,
},
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
posts(first: $count) {
pageInfo {
hasNextPage
}
edges {
node {
id,
body
},
},
},
},
`,
},
});
Theses posts are organized by the most recent ones, how can I check if there are newer posts in my graphql server? I want to do a pulling strategy until I migrate it to graphql/relay subscriptions
Can I use forceFetch? I don't wanna to update Relay Store, I just wanna to notify the user that has new posts
One way I could think of is to have an endpoint which you supply with the latest timestamp. This endpoint returns for example the ids of the newer posts. On relay side you do a setTimeout(() => { this.props.relay.setVariables({timestamp: timestamp}); }, 60000)
and in the fragment something like:
const FooBar = Relay.createContainer(FooBarClass, {
initialVariables: {
timestamp: null
},
prepareVariables: vars => {
vars['timestampIsTruthy'] = !!vars['timestamp'];
return vars;
},
fragments: {
store: () => Relay.QL`
fragment on Query {
newerPosts(timestamp: $timestamp, first: 3) #include(if: $queryIsTruthy) {
totalCount
edges {
node {
id
}
}
}
}`,
},
});