Accessing data from array within Gatsby Graphql query - reactjs

Im getting stumped here. I have a Gatsby site Im setting up with netlify cms. I have my config file set and I can create the pages and widgets from within netlify admin. When I run my graphql query, I can pull the page info, but I cant access the data from the widget.
admin/config.yml
name: git-gateway
branch: master # Branch to update (optional; defaults to master)
media_folder: "src/images" # Media files will be stored in the repo under static/images/uploads
collections:
- name: "pages"
label: "Page"
folder: "src/markdown-pages"
create: true
fields:
- {label: "Title", name: "title"}
- {label: "Content", name: "content"}
- {label: "Slug", name: "slug"}
- {label: "Path", name: "path"}
- label: "Page Sections"
name: "sections"
widget: "list"
types:
- label: "Call To Action"
name: "calltoaction"
widget: object
fields:
- {label: "Title", name: "title", widget: "string"}
- {label: "Content", name: "content", widget: "markdown", required: false}
- {label: "Call to Action Link", name: "link", widget: "string", required: false, hint: "Call to Action Link"}
- {label: "Call to Action Text", name: "buttontext", widget: "string", required: false, hint: "Call to Action Text"}
- label: "Services"
name: "services"
widget: object
fields:
- {label: "Title", name: "title", widget: "string"}
- {label: "Content", name: "content", widget: "markdown", required: false}
gatsby-node.js
exports.createPages = ({actions, graphql}) => {
const {createPage} = actions;
const pageTemplate = path.resolve('src/templates/page.js');
return graphql(`{
allMarkdownRemark {
edges {
node {
id
frontmatter {
title
path
sections {
buttontext
content
link
title
}
}
}
}
}
}`)
.then(result => {
if (result.errors) {
result.errors.forEach(e => console.error(e.toString()))
return Promise.reject(result.errors)
}
result.data.allMarkdownRemark.edges.forEach(({node}) => {
createPage({
path: node.frontmatter.path,
component: pageTemplate,
context: {
id: node.id,
}
})
})
})
}
pagetemplate.js
import Helmet from 'react-helmet'
import Layout from "../components/layout"
//import CTA from '../components/sections/CTA-section'
export default function Template({ data }) {
const {markdownRemark: page} = data
return (
<Layout>
<h1>{page.frontmatter.title}</h1>
<p>{page.frontmatter.sections.title}</p>
// <CTA />
</Layout>
)
}
export const pageQuery = graphql`
query pageByPath($id: String!) {
markdownRemark(id: { eq: $id } ) {
id
frontmatter {
path
title
sections {
title
buttontext
content
link
}
}
}
}
`
GraphiQL screenshot
graphiql screenshot
Bonus question: I also need to figure out how to make the component for these widgets. As you can see from the commented out lines in pagetemplate.js I would like to make separate components for each widget. Since the data is dynamic, I dont think I will be able to use StaticQuery, and I cant make another page query on the same data since it will be imported.

Related

How to render code block in nextjs / sanity

I am building a blog website using Sanity and NextJS.
I've created this object in the sanity schema.
{
name: "content",
title: "Content",
type: "array",
of: [{
type: "block"
},
{
name: "myCode",
title: "Code",
type: "code"
}
]
}
I am fetching the data using import { createClient } from "next-sanity"; and using import PortableText from "react-portable-text";; to render the objects received from sanity.
{
_createdAt: '2022-09-18T13:16:41Z',
_id: '1f6b2ee7-4a2b-40e7-805e-b00208dfe5d0',
_rev: 'XOYlEtECWeMhp8Pp0lwIEq',
_type: 'blog',
_updatedAt: '2022-09-21T09:50:25Z',
content: [
{
_key: 'd9fba48f3f71',
_type: 'myCode',
code: '"""\nThis is the first part of code in python\n"""',
language: 'python'
},
{
_key: '6a037159196b',
_type: 'block',
children: [Array],
markDefs: [],
style: 'normal'
},
{
_key: 'ad404d6059fe',
_type: 'myCode',
code: 'def fun():\n print("PYFUN")',
language: 'python'
},
{
_key: 'f8e723dbff93',
_type: 'block',
children: [Array],
markDefs: [],
style: 'h1'
}
]
}
Now, I want to render the blog.content section using PortableText.
Every object is rendering, except the object inside the blog.content with _type="myCode".
Is there a specific way to render the _type="myCode"? any help is really appriciated.

Type has no properties in common with type 'ColumnType<any>'

I am writing an app with react and ant design
I want to use antd table
but it does not show my data
Code
const ListOfProperties = () => {
const columns = [
{
type: "Type",
state: "State",
location: "Location",
},
];
const properties = [
{
type: "Flat",
state: "Anambra",
location: "Udoka",
},
{
type: "Flat",
state: "Anambra",
location: "Udoka",
},
];
return (
<Skeleton active loading={loading}>
<Table columns={columns} dataSource={properties} />
</Skeleton>
);
};
export default ListOfProperties;
I created an array of objects which I passed to the data source and columns to the columns
as stated in the docs
but I am getting error
Error
Type '{ type: string; state: string; location: string; }' has no properties in common with type 'ColumnType<any>'.
you need to provide title, dataIndex and key. Always look it up in the documentation first.
const columns = [
{
title: "Type",
dataIndex: "type",
key: "type"
},
{
title: "State",
dataIndex: "state",
key: "state"
},
{
title: "Location",
dataIndex: "location",
key: "location"
},
];

Change all keys in array also deeper inside object with array

Currently for our navigation structure we have the following array:
{
childNodes: [
{title: "Mijn afdeling", type: "Department", relativeUri: "/mijnafdeling"},
{title: "contact", type: "Department", relativeUri: "/contact"}
]
RelativeUri: '/',
title: 'test'
},
{
childNodes: null
RelativeUri: '/',
title: 'test'
}
]
Now I want to change all the 'childNodes' keys to 'child' and also all the relativeUri keys to href.
My desired result should be
{
child: [
{title: "Mijn afdeling", type: "Department", href: "/mijnafdeling"},
{title: "contact", type: "Department", href: "/contact"}
]
href: '/',
title: 'test'
},
{
child: null
href: '/',
title: 'test'
}
]
I know that I can do something like this.navigationArray = this.navigationArray.map(({ childNodes, relativeUri }) => ({ childNodes: child, href: relativeUri })) However I don't know how to get all the keys in the array under the childNodes object too. So it basically needs to 'find' or loop over all the objects inside the array, and find the specic keys. Also a 'childNodes' object can be null.
For different pages I have a different number of navigation items, so I rather don't target a specific index as that can mess up the navigation.
Thanks a lot!
This should do it:
const nu = orig
.map(
({
childNodes,
RelativeUri,
title
}) => ({
child: childNodes ?
childNodes.map(({
title,
type,
relativeUri
}) => ({
title,
type,
href: relativeUri
})) : childNodes,
href: RelativeUri,
title
}));
console.log(nu);
DEMO
const orig = [
{
childNodes: [{
title: "Mijn afdeling",
type: "Department",
relativeUri: "/mijnafdeling"
},
{
title: "contact",
type: "Department",
relativeUri: "/contact"
}
],
RelativeUri: '/',
title: 'test'
},
{
childNodes: null,
RelativeUri: '/',
title: 'test'
}
];
//console.log( orig );
const nu = orig
.map(({childNodes,RelativeUri,title}) => ({child: childNodes ? childNodes.map(({title,type,relativeUri}) => ({title,type,href:relativeUri})) : childNodes, href:RelativeUri, title}));
console.log( nu );

Grabbing data from Netlify CMS folder

I have successfully linked the netlify cms backend to my react frontend, however now my main problem is figuring out how to display said data from its folder _posts to my react frontend.
MySite
| _posts
| blog
/2020-06-23-my-post.md
| public
config.yml
backend:
name: git-gateway
branch: master
media_folder: "public/images" # Media files will be stored in the repo under static/images/uploads
public_folder: "/images"
collections:
- name: "blog" # Used in routes, e.g., /admin/collections/blog
label: "Blog" # Used in the UI
folder: "_posts/blog" # The path to the folder where the documents are stored
create: true # Allow users to create new documents in this collection
slug: "{{year}}-{{month}}-{{day}}-{{slug}}" # Filename template, e.g., YYYY-MM-DD-title.md
fields: # The fields for each document, usually in front matter
- {label: "Layout", name: "layout", widget: "hidden", default: "blog"}
- {label: "Title", name: "title", widget: "string"}
- {label: "Publish Date", name: "date", widget: "datetime"}
- {label: "Featured Image", name: "thumbnail", widget: "image"}
- {label: "Rating (scale of 1-5)", name: "rating", widget: "number"}
- {label: "Body", name: "body", widget: "markdown"}
Assume you have set your static/admin/config.yml for blog post like below
backend:
name: github
repo: ozluy/destan-nakliyat-gatsby
branch: master
media_folder: static/assets
public_folder: /assets
site_url: https://destan-nakliyat.netlify.app
collections:
- name: post
label: Post
folder: post
create: true
fields:
- { name: path, label: Path }
- { name: date, label: Date, widget: datetime }
- { name: title, label: Title }
- { name: body, label: Body, widget: markdown }
with gatsby-source-filesystem plugin you can use set your plugin settings as below in gatsby-config.js like this:
plugins: [
...,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/post/`,
},
},
finally you can access saved post files through GraphQL as below:
{
allFile {
edges {
node {
extension
dir
modifiedTime
}
}
}
}

Displaying contents of directory(markdown files) in Gatsby using graphql

I'm using Gatsby/Netlify CMS stack and have been trying to display markdown file contents on the main page. For example, I have a directory in src/pages/experience that displays all of the experience markdown files.
So using graphql, I have a query that actually works:
{
allMarkdownRemark(
limit: 3,
sort: { order: DESC, fields: [frontmatter___date] },
filter: { fileAbsolutePath: { regex: "/(experience)/" } }
) {
edges {
node {
id
frontmatter {
title
company_role
location
work_from
work_to
tags
}
excerpt
}
}
}
}
However, when running it on my component page it displays ×
TypeError: Cannot read property 'allMarkdownRemark' of undefined
However after entering this before return:
if (!data) { return null };
The error goes away but the entire section disappears. Here it is below:
const Experience = ({data}) => {
return (
<div id="experience" className="section accent">
<div className="w-container">
<div className="section-title-group">
<Link to="#experience"><h2 className="section-heading centered">Experience</h2></Link>
</div>
<div className="columns w-row">
{data.allMarkdownRemark.edges.map(({node}) => (
<div className="column-2 w-col w-col-4 w-col-stack" key={node.id}>
<div className="text-block"><strong>{node.frontmatter.title}</strong></div>
<div className="text-block-4">{node.frontmatter.company_role}</div>
<div className="text-block-4">{node.frontmatter.location}</div>
<div className="text-block-3">{node.frontmatter.work_from} – {node.frontmatter.work_to}</div>
<p className="paragraph">{node.frontmatter.excerpt}</p>
<div className="skill-div">{node.frontmatter.tags}</div>
</div>
))}
</div>
</div>
</div>
)}
export default Experience
In gatsby-config-js, I've added a gatsby-source-filesystem resolve separate from /src/posts to /src/pages where the experience directory is src/pages/experience.
Update: 2/7/2019
Here is the gatsby-config-js file:
module.exports = {
siteMetadata: {
title: `Howard Tibbs Portfolio`,
description: `This is a barebones template for my portfolio site`,
author: `Howard Tibbs III`,
createdAt: 2019
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
{
resolve: 'gatsby-remark-images',
},
],
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/posts`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages`,
},
},
`gatsby-plugin-netlify-cms`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
`gatsby-transformer-sharp`
],
}
What I feel is that somewhere in gatsby-node-js, I did not create a instance to do something with that type query.
const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')
const PostTemplate = path.resolve('./src/templates/post-template.js')
const BlogTemplate = path.resolve('./src/templates/blog-template.js')
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === 'MarkdownRemark') {
const slug = createFilePath({ node, getNode, basePath: 'posts' })
createNodeField({
node,
name: 'slug',
value: slug,
})
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
{
allMarkdownRemark (limit: 1000) {
edges {
node {
fields {
slug
}
}
}
}
}
`)
const posts = result.data.allMarkdownRemark.edges
posts.forEach(({ node: post }) => {
createPage({
path: `posts${post.fields.slug}`,
component: PostTemplate,
context: {
slug: post.fields.slug,
},
})
})
const postsPerPage = 2
const totalPages = Math.ceil(posts.length / postsPerPage)
Array.from({ length: totalPages }).forEach((_, index) => {
const currentPage = index + 1
const isFirstPage = index === 0
const isLastPage = currentPage === totalPages
createPage({
path: isFirstPage ? '/blog' : `/blog/${currentPage}`,
component: BlogTemplate,
context: {
limit: postsPerPage,
skip: index * postsPerPage,
isFirstPage,
isLastPage,
currentPage,
totalPages,
},
})
})
}
Wanted to know if anyone was able to get something similar to work? Greatly appreciate your help.
Update: 2/6/2019
So made some changes to my code from pageQuery to StaticQuery and unfortunately it still doesn't work but I believe it is going the right direction:
export default() => (
<div id="experience" className="section accent">
<div className="w-container">
<div className="section-title-group">
<Link to="#experience"><h2 className="section-heading centered">Experience</h2></Link>
</div>
<div className="columns w-row">
<StaticQuery
query={graphql`
query ExperienceQuery {
allMarkdownRemark(
limit: 2,
sort: { order: DESC, fields: [frontmatter___date]},
filter: {fileAbsolutePath: {regex: "/(experience)/"}}
) {
edges {
node {
id
frontmatter {
title
company_role
location
work_from
work_to
tags
}
excerpt
}
}
}
}
`}
render={data => (
<div className="column-2 w-col w-col-4 w-col-stack" key={data.allMarkdownRemark.id}>
<div className="text-block"><strong>{data.allMarkdownRemark.frontmatter.title}</strong></div>
<div className="text-block-4">{data.allMarkdownRemark.frontmatter.company_role}</div>
<div className="text-block-4">{data.allMarkdownRemark.frontmatter.location}</div>
<div className="text-block-3">{data.allMarkdownRemark.frontmatter.work_from} – {data.allMarkdownRemark.frontmatter.work_to}</div>
<p className="paragraph">{data.allMarkdownRemark.frontmatter.excerpt}</p>
<div className="skill-div">{data.allMarkdownRemark.frontmatter.tags}</div>
</div>
)}
/>
</div>
</div>
</div>
);
I get this error TypeError: Cannot read property 'title' of undefined
So what I'm trying to accomplish is this instance across this section. Of course this is a placeholder but I'm looking to replace that placeholder with the contents of each markdown.
Experience snip
Update: 2/7/2019
So no changes today but wanted to post a few fields to get a better perspective of what I'm trying to do. This is the config.yml file from NetlifyCMS where it is displaying the collections. This is what I'm accomplishing (Note: the test repo is just to see the actual CMS, I will look to change):
backend:
name: test-repo
branch: master
media_folder: static/images
public_folder: /images
display_url: https://gatsby-netlify-cms-example.netlify.com/
# This line should *not* be indented
publish_mode: editorial_workflow
collections:
- name: "experience"
label: "Experience"
folder: "experience"
create: true
fields:
- { name: "title", label: "Company Title", widget: "string" }
- { name: "company_role", label: "Position Title", widget: "string" }
- { name: "location", label: "Location", widget: "string" }
- { name: "work_from", label: "From", widget: "date", format: "MMM YYYY" }
- { name: "work_to", label: "To", default: "Present", widget: "date", format: "MMM YYYY" }
- { name: "description", label: "Description", widget: "text" }
- { name: "tags", label: "Skills Tags", widget: "select", multiple: "true",
options: ["ReactJS", "NodeJS", "HTML", "CSS", "Sass", "PHP", "Typescript", "Joomla", "CMS Made Simple"] }
- name: "blog"
label: "Blog"
folder: "blog"
create: true
slug: "{{year}}-{{month}}-{{day}}_{{slug}}"
fields:
- { name: path, label: Path }
- { label: "Image", name: "image", widget: "image" }
- { name: title, label: Title }
- { label: "Publish Date", name: "date", widget: "datetime" }
- {label: "Category", name: "category", widget: "string"}
- { name: "body", label: "body", widget: markdown }
- { name: tags, label: Tags, widget: list }
- name: "projects"
label: "Projects"
folder: "projects"
create: true
fields:
- { name: date, label: Date, widget: date }
- {label: "Category", name: "category", widget: "string"}
- { name: title, label: Title }
- { label: "Image", name: "image", widget: "image" }
- { label: "Description", name: "description", widget: "text" }
- { name: body, label: "Details", widget: markdown }
- { name: tags, label: Tags, widget: list}
- name: "about"
label: "About"
folder: "src/pages/about"
create: false
slug: "{{slug}}"
fields:
- {
label: "Content Type",
name: "contentType",
widget: "hidden",
default: "about",
}
- { label: "Path", name: "path", widget: "hidden", default: "/about" }
- { label: "Title", name: "title", widget: "string" }
- { label: "Body", name: "body", widget: "markdown" }
And for an example of a markdown page, this would be the format to look for in the Experience section, because as you see in the picture it displays across the container:
---
title: Test Company
company_role: Test Role
location: Anytown, USA
work_from: January, 2020
work_to: January, 2020
tags: Test, Customer Service
---
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
Update: 2/8/2019
I do have some updates with the code provided below but before I get into it, here are a few images of what I'm looking to accomplish. These are placeholders that I'm looking to replace for real data. This is for each section:
Full Experience snip
Projects snip
Blog snip
I've ran the code that was provided by #staypuftman in the answer below and came up with this error:
Your site's "gatsby-node.js" created a page with a component that
doesn't exist.
I've added the code in addition to what was already there and it processed that error. This is what I originally thought would happen and the reason I wanted to use StaticQuery independently. This was actually the main issue I had with the documentation and the starter repos, no one really has created multiple variables in node.js.
I also tried the revision from #DerekNguyen which looked like this:
import React from "react"
import { Link, graphql, StaticQuery } from "gatsby"
export default(data) => (
<div id="experience" className="section accent">
<div className="w-container">
<div className="section-title-group">
<Link to="#experience"><h2 className="section-heading centered">Experience</h2></Link>
</div>
<div className="columns w-row">
<StaticQuery
query={graphql`
query ExperienceQuery {
allMarkdownRemark(
limit: 2,
sort: { order: DESC, fields: [frontmatter___date]},
filter: {fileAbsolutePath: {regex: "/(experience)/"}}
) {
edges {
node {
id
frontmatter {
title
company_role
location
work_from
work_to
tags
}
excerpt
}
}
}
}
`}
render={data.allMarkdownRemark.edges.map(({ node }) => (
<div className="column-2 w-col w-col-4 w-col-stack" key={node.id}>
<div className="text-block"><strong>{node.frontmatter.title}</strong></div>
<div className="text-block-4">{node.frontmatter.company_role}</div>
<div className="text-block-4">{node.frontmatter.location}</div>
<div className="text-block-3">{node.frontmatter.work_from} – {node.frontmatter.work_to}</div>
<p className="paragraph">{node.frontmatter.excerpt}</p>
<div className="skill-div">{node.frontmatter.tags}</div>
</div>
))}
/>
</div>
</div>
</div>
);
However that also came with an error as well:
TypeError: Cannot read property 'edges' of undefined
Still working on it, but I think it is getting closer to the solution. Keep in mind I also would have to create it for the other variables.
Update: 2/10/2019
For those who want to see how I constructed the site using gatsby-starter, here it is below:
My portfolio
gastby-node.js is used when you have a bunch of pages that need to be at /pages/{variable-here}/. Gatsby uses gatsby-node.js to run a GraphQL query against your data source (Netlify CMS in this case) and grabs all the content needed based on your particular GraphQL query.
It then dynamically builds out X number of pages using a component in your project. How many pages it builds depends on what it finds at the remote data source. How they look depends on the component you specify. Read more about this in the Gatsby tutorial.
Staticquery is used to get one-time data into components, not to generate pages from a data source. It is highly useful but not what I think you're trying to do. Read more about it on the Gatsby site.
Based on all of this and what you've provided above, I think your gatsby-node.js should look this:
// Give Node access to path
const path = require('path')
// Leverages node's createPages capabilities
exports.createPages = async ({ graphql, actions }) => {
// Destructures createPage from redux actions, you'll use this in a minute
const { createPage } = actions
// Make your query
const allExperiencePages = await graphql(`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
id
frontmatter {
title
company_role
location
work_from
work_to
tags
}
excerpt
}
}
}
}
`)
// gatsby structures allExperiencePages into an object you can loop through
// The documentation isn't great but the 'data' property contains what you're looking for
// Run a forEach with a callback parameter that contains each page's data
allExperiencePages.data.allMarkdownRemark.edges.forEach( page => {
// Make individual pages with createPage variable you made earlier
// The 'path' needs to match where you want the pages to be when your site builds
// 'conponent' is what Gatsby will use to build the page
// 'context' is the data that the component will receive when you `gatsby build`
createPage({
path: `/pages/${page.node.title}/`,
component: path.resolve('src/components/Experience'),
context: {
id: page.node.id,
title: page.node.frontmatter.title,
company_role: page.node.frontmatter.company_role,
location: page.node.frontmatter.location,
work_from: page.node.frontmatter.work_from,
work_to: page.node.frontmatter.work_to,
tags: page.node.frontmatter.tags,
excerpt: page.node.excerpt
}
})
})
}
This alone may not be enough to generate a page! It all depends on what's going on with the component you specify in the createPage component part of the gatsby-node.js file.

Resources