Programatically generating Gatsby pages without a slug from Graphql - javascript

I have set up an ACF options page in WordPress called Projects
Inside the Projects options page there is an ACF repeater allowing the user to add multiple Projects.
In Gatsby, I’m using Graphql to query the data for my Projects in two files:
Inside a custom hook, allowing access to the data globally in my Gatsby site
Inside a gatsby-node.js file in order to generate a slug for my template page called project-details.js
Obviously there is no slug in Graphql for this repeater field in the ACF options page. Instead, I’m generating a slug based on a nested Title text field that’s found inside each Project repeater field.
I’m using both the replaceAll() and toLowerCase() methods to create the slug and then making it available as part of my data.
Here's my custom hook:
export const useProjectsQueryAlt = () => {
const data = useStaticQuery(graphql`
query ProjectsQueryAlt {
wp {
projects {
projects {
allprojects {
projectContent
projectTitle
featuredImage {
mediaItemUrl
id
}
projectGallery {
caption
id
mediaItemUrl
}
}
}
}
}
}
`)
const project = data.wp.projects.projects.allprojects.map(node => {
const { projectContent, projectTitle, featuredImage, projectGallery } = node;
const title = node.projectTitle;
const spacesToHyphen = title.replaceAll(' ', '-');
const slugFromTitle = spacesToHyphen.toLowerCase()
return {
projectContent,
projectTitle,
slug: slugFromTitle,
featuredImage,
projectGallery: projectGallery.map(node => {
const { caption, id, mediaItemUrl } = node;
return {
caption,
id,
mediaItemUrl
}
})
}
})
return { project }
}
Here's my gatsby-node file:
const path = require('path')
exports.createPages = async ({ graphql, actions }) => {
const { data } = await graphql(`
query Projects {
wp {
projects {
projects {
allprojects {
projectTitle
}
}
}
}
}
`)
data.wp.projects.projects.allprojects.forEach(node => {
const title = node.projectTitle;
const spacesToHyphen = title.replaceAll(' ', '-');
const slugFromTitle = spacesToHyphen.toLowerCase()
actions.createPage({
path: '/projects/' + slugFromTitle,
component: path.resolve('./src/templates/project-details.js'),
context: { slug: slugFromTitle },
})
})
}
Here's my template file project-details.js
import React from 'react'
function ProjectDetails() {
return (
<div>
...my page template content
</div>
)
}
export default ProjectDetails
I now need to find a way to check that the two appended slugs match in my ‘project-details.js’ template file in order to display the relevant project data to the corresponding URL.
Seeing as I’ve generated my slugs on the front end, following the Gatsby Docs for setting up dynamically generate pages doesn’t align with my use case. I was hoping somebody has had experience with this use case and can point me in the right direction.

The problem in your approach is that you are generating a "fake" slug based on the title of the project so you can't use that field to filter any GraphQL node because the field is not present in the project fields. Your best option is using the title itself or using any autogenerated identifier (id, if it's present as a field).
actions.createPage({
path: '/projects/' + slugFromTitle,
component: path.resolve('./src/templates/project-details.js'),
context: { title },
})
Note: you can omit { title: title }
You can still use the path of your generated slug, this is a valid approach.
I'm assuming that if the title is a unique field, the slug must be too, hence you will be a valid filter.
Now in the project-details.js:
import React from 'react'
function ProjectDetails({ data }) {
console.log("my data is", data);
return (
<div>
...my page template content
</div>
)
}
export const query = graphql`
query($title: String!) {
yourACFNode(title: { eq: $title} ) {
# your fields
}
}
`
export default ProjectDetails
Of course, tweak the query above to match your ACF node but get the approach.

Related

Gatsby category slugs not working from dynamic category page

In my gatsby-node.js I create dynamic Category pages:
exports.createPages = async ({ graphql, actions: { createPage } }) => {
const {
data: { projects, categories },
} = await graphql(`
query Projects {
projects: allGraphCmsProject(filter: { stage: { eq: PUBLISHED } }) {
nodes {
id
slug
}
}
categories: allGraphCmsCategory {
nodes {
id
slug
}
}
}
`);
projects.nodes.forEach(({ id, slug }) => {
createPage({
path: `${slug}`,
component: path.resolve('./src/templates/ProjectPage.tsx'),
context: { id, slug },
});
});
categories.nodes.forEach(({ id, slug }) => {
createPage({
path: `/category/${slug}`,
component: path.resolve('./src/templates/CategoryPage.tsx'),
context: { id },
});
});
};
Inside src/templates/CategoryPage.tsx I render a CategoryList.tsx component.
In the browser on the page /category (src/pages/category.tsx), I render also the list categories (CategoryList.tsx component). When I click a category from this page it's working fine and it shows a url like /category/category-one and shows the categorie page in the browser.
But then if I click another category (from within a category page (src/templates/CategoryPage.tsx), I get an url like /category/category-one/category-two?
But then if I click another category (from within a category page
(src/templates/CategoryPage.tsx), I get an url like
/category/category-one/category-two?
Your page generation looks good, except some unnecessary template literals:
projects.nodes.forEach(({ id, slug }) => {
createPage({
path: slug, // before was path: `${slug}`,
component: path.resolve('./src/templates/ProjectPage.tsx'),
context: { id, slug },
});
});
Your issue appears because you are missing an initial slash (/) at the beginning of the to props of the <Link> component.
On your category page:
<Link to={`/${slug}`}/>{categoryName}</Link>
Note: I don't know your page structure but the goal is to add an initial slash.
This is because slug, may come or not with an initial slash. If it doesn't, it will concatenate the current URL to the slug itself, like an anchor (<a>) normally does.

Framework7 + Vue + Firebase: Open an individual page displaying the dynamic info from the database?

How to I get to open a user with a dynamic url each to an individual page?
I use Framework7 + Vue for a Cordova Project.
I have customized the default product page that grabs an id and display data of a single product, It works by generating the unique ID (“uid” as per firebase) for each user in a url by it fails to pass it the user.vue page that I created, it doesn’t open when I click on it. What exactly could I be missing here? I don’t know much about JSON.stringify stuffs!
user.vue - the individual data page
<f7-page name="user">
<f7-navbar :title="user.username" back-link="Back"></f7-navbar>
<f7-block-title>About {{user.username}}</f7-block-title>
<f7-block strong>
{{user.email}}
</f7-block>
</f7-page>
<script>
export default {
data: function () {
var userId = this.$f7route.params.id;
var currentUser;
this.$f7.data.users.forEach(function (user) {
if (user.user_id === userId) {
currentUser = user;
}
});
return {
user: currentUser,
};
}
};
</script>
home.vue - the user list loop, working just fine as expected
<f7-list>
<f7-list-item
v-for="(user) in users"
:key="user.user_id"
:title="user.username"
:link="`/user/${user.user_id}/`"
></f7-list-item>
</f7-list>
home.vue - mounted functions script
mounted() {
let viewUsers = this;
const usersRef = firebase.database().ref("users");
usersRef.on("value", snapshot => {
let data = snapshot.val();
let users = [];
Object.keys(data).forEach(key => {
users.push({
id: key,
username: data[key].username,
user_email: data[key].email,
user_id: data[key].uid,
});
});
viewUsers.users = users;
});
}
home.vue - data returned script at the top
data() {
return {
users: this.$f7.data.users, //the arrays had already been called/initialized at the app.vue
}
routes.js:
import UserPage from ‘…/pages/user.vue’;
{
path: ‘/user/:id/’,
component: UserPage,
}
What exactly am i missing here?

Generate new page after slug

I am building a NextJS application, currently I am using getStaticPaths and getStaticProps to build the static pages, doing the necessary requests for them.
So I want to build all the pages following this url: challenge/[slug]/ and for each slug that corresponds to an id I want to have a applications page like this: challenge/[slug]/applications to archive this I builded a file [...slug] inside /pages/challenge
Inside that file I have the following code to handle the static generation:
export async function getStaticPaths() {
const response: any = await getPrograms()
const paths = response.results.map(result => {
return { params: { slug: [result.id.toString()] } }
})
return { paths, fallback: true }
}
export async function getStaticProps({ params }) {
const res = await getProgram(params.slug[0])
const stages = await getStages(params.slug[0])
return { props: { program: res, stages: stages }, revalidate: 1 }
}
this solution works for /challenge/[slug], but the /challenge/[slug]/applications receives a 404, how can I render a specific application page for the slug?
I tried to add a second position to the slug array, but if I do it I can just render /challenge/[slug]/applications and not /challenge/[slug]
Any advice?
Thanks!
Firstly, You need to create a FOLDER named [slug]. Then, Create a FILE named applications.js. Lastly, copy and paste that code into this page.
__ challenge
|__ [slug]
|__ applications
In this page you can get or set slug as your desired parameter.

CraftCMS Gatsby project throwing error "GraphQL Error Expected type [String], found {eq: $slug}."

I just started working with Gatsby to see if it would be a good choice to rebuild my company's CraftCMS website with Craft as the backend and Gatsby as the frontend. So far everything has been working well until it came time to query for the individual entries inside our "campaign" channel.
For the record, I have been able to render a complete list using .map() for each of my campaign entries on a "overall view" page to see all the campaigns. I have also been able to recursively build out each campaign page so that it calls my /src/templates/campaign-page.js template and has the correct slug pulled from my site's Craft API with no issue. For some reason, I just can't get my individual campaign data to query inside the campaign-page.js template.
I've read just about every page in the Gatsby docs and every tutorial that currently exists, but for the life of me I can't figure out why my GraphQL query will not filter for my individual campaign entries. It just keeps telling me, "GraphQL Error Expected type [String], found {eq: $slug}."
I've also tried wrapping my "slug: {eq: $slug} in a "filter:" based on some markdown docs, but that just tells me "filter" does not exist. I'm beginning to think the issue is in my gatsby-node.js file, but I'm not seeing any issue when I compare it to the docs.
Gatsby-node.js
const path = require(`path`)
exports.createPages = async ({ actions, graphql }) => {
const { data } = await graphql(`
query {
api {
entries(section: "campaigns") {
slug
}
}
}
`)
data.api.entries.forEach(({ slug }) => {
actions.createPage({
path: "/campaigns/" + slug,
component: path.resolve(`./src/templates/campaign-page.js`),
context: {
slug: slug,
},
})
})
}
Campaign-page.js
export default ({data}) => {
const post = data.api.entries
return(
<div className={"campaign-page-single"} style={{marginTop: '-21px,'}}>
<Header/>
<div>
<h1>{post.id}</h1>
</div>
</div>
)
}
export const campaignQuery = graphql`
query ($slug: String!){
api{
entries (slug: { eq: $slug }){
slug
id
... on API_campaigns_campaigns_Entry {
id
campaignTitle
slug
}
}
}
}
`
For reference, here's what a typical working query looks like on my main campaigns.js page that lists all available campaigns:
query = {graphql`
{
api {
entries(section: "campaigns") {
... on API_campaigns_campaigns_Entry {
id
campaignTitle
uri
slug
}
}
}
}
`}
I expect my /src/templates/campaign-page.js template to render the individual campaign data.
I finally had one of my coworkers take a look at my code. All I had to do was wrap my $slug variable in brackets as so:
entries (section: "campaigns", slug: [$slug] )
That's two days I wish I could have back.

Gatsby dynamic content and schema stitching

I have the following graphql query that is executing at build time from my gatsby-node.js file that I'm using to bring in my article data. Is there a way to use Apollo/Axios to retrieve new articles without having to rebuild the site essentially rehydrating my site in between builds? Any help is greatly appreciated!!
I'm using Gatsby v2, Drupal as my CMS and GraphQL.
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions;
const blogTemplate = path.resolve('src/templates/blog-post.js');
return graphql(`
{
blog: allNodeArticle{
edges {
node {
id
path {
alias
}
}
}
}
`
).then(result => {
if (result.errors) {
Promise.reject(result.errors);
}
// Create blog pages
result.data.blog.edges.forEach(({ node }) => {
createPage({
path: node.path.alias,
component: blogTemplate,
context: {
alias: node.path.alias,
},
});
});
});
}
I would like to merge in the new data as it becomes available while keeping older data completely static (massive benefit of Gatsby)

Categories