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.
Related
I am unable to make my local images render in the development server of my gatsby website. I am using gatsby and tailwindcss. I am trying to combine a filterable photo gallery and a light box. My problem is that the file paths do not work unless I use the url through graphql. I think this also affecting my light box because if you click on an image it will not display it and also show image not found for the light box.
I have tried to configure the gatsby-source-filesystem file path to access my images folder in my src. I also matched the array source path to the /src/images/mbaglass1.jpeg. This rendered the alt tag.
I have tried to import the file path through the allFile in grapql. This is my first time using it. I was unable to render the images on the page in the manner I require.
I did import the url from graphql which is successfully rendering the images on the page, but I am unable to make it function with my light box.
I have created a functioning light box for a different website and I have been able to make this filter function in this website, but I could use some guidance towards successfully combining the two.
Here is the component:
import ImageLightbox from "react-image-lightbox"
import React from "react"
const images = [
{
id: 1,
src: "/src/images/mbaglass1.jpeg",
category: "Frameless Showers",
alt: "shower",
},
{
id: 3,
src: "/static/ddb59dd079ddf9b6b0caebc1a9f05078/mbaglass3.jpeg",
category: "Frameless Showers",
},
{
id: 2,
src: "/static/bbb4f3909b9421b16d474e1fa01805c4/mbaglass2.jpeg",
category: "Mirrors",
},
]
const categories = [
"All",
"Frameless Showers",
"Sliding Showers",
"Mirrors",
"Miscellaneous",
]
class GalleryDisplay extends React.Component {
constructor(props) {
super(props)
this.state = {
lightboxIsOpen: false,
currentImage: 0,
currentCategory: categories[0],
}
this.openLightbox = this.openLightbox.bind(this)
this.closeLightbox = this.closeLightbox.bind(this)
this.moveNext = this.moveNext.bind(this)
this.movePrev = this.movePrev.bind(this)
this.handleFilterChange = this.handleFilterChange.bind(this)
}
openLightbox(e, { index }) {
this.setState({
currentImage: index,
lightboxIsOpen: true,
})
}
closeLightbox() {
this.setState({
currentImage: 0,
lightboxIsOpen: false,
})
}
moveNext() {
this.setState({
currentImage: (this.state.currentImage + 1) % images.length,
})
}
movePrev() {
this.setState({
currentImage:
(this.state.currentImage - 1 + images.length) % images.length,
})
}
handleFilterChange(event) {
this.setState({
currentCategory: event.target.value,
})
}
render() {
<div>
<ImageLightbox
isOpen={this.state.lightboxIsOpen}
mainSrc={images[this.state.currentImage]}
nextSrc={images[(this.state.currentImage + 1) % images.length]}
prevSrc={
images[(this.state.currentImage - 1 + images.length) % images.length]
}
onCloseRequest={this.closeLightbox}
onMovePrevRequest={this.movePrev}
onMoveNextRequest={this.moveNext}
/>
</div>
return (
<>
<div className="flex flex-wrap justify-center py-4">
{categories.map(category => (
<button className="px-4 py-2 m-2 rounded-lg text-center text-white bg-blue-500 hover:bg-blue-600" onClick={() => this.handleFilterChange({target: {value: category}})}>{category}</button>
))}
</div>
<div className="grid p-8 lg:p-16 gap-x-4 gap-y-8 sm:grid-cols-0 sm:gap-x-4 md:grid-cols-2 md:gap-x-8 lg:grid-cols-3 xl:gap-x-12" >
{images
.filter(
image =>
image.category === this.state.currentCategory ||
this.state.currentCategory === "All"
)
.map((image, index) => (
<a key={index} onClick={e => this.openLightbox(e, { index })}>
<img key={image.src} className="rounded-lg" src={image.src} alt={image.alt} />
</a>
))}
</div>
</>
)
}
}
export default GalleryDisplay;
Here is the gatsby-config file:
/**
* Configure your Gatsby site with this file.
*
* See: https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/
*/
/**
* #type {import('gatsby').GatsbyConfig}
*/
module.exports = {
siteMetadata: {
siteMetadata: {
title: `Gatsby`,
siteUrl: `https://www.gatsbyjs.com`,
description: `Blazing fast modern site generator for React`,
},
},
plugins: [
`gatsby-plugin-image`,
'gatsby-plugin-postcss',
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
// This will impact how browsers show your PWA/website
// https://css-tricks.com/meta-theme-color-and-trickery/
// theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
],
}
I was able to resolve this by first importing the image at the top of the component e.g. import mbaglass2 from "../mbaglass2.jpeg", then using its import name mbaglass2 in the array source. It seems it was a simple fix that way overlooked.
I am trying to create FAQ page in my react project using below package:
https://www.npmjs.com/package/react-faq-component
I am able to show FAQ with 1 category.
I want to show questions/answers with different categories:
Code:
import React, { useState } from 'react';
import FaqData from 'react-faq-component';
function Faq() {
const [rows, setRowsOption] = useState(null);
const data = {
title: 'FAQ (how it works)',
rows: [
{
title: 'How do I change my password?',
content: `Answer here.`,
},
{
title: 'How do I sign up?',
content:'Answer here.',
},
],
};
return (
<div>
<h2 className="section-title">My FAQ's</h2>
<div className="faq-style-wrapper">
<FaqData data={data} getRowOptions={setRowsOption} />
</div>
</div>
);
}
If any other demo/library can give me desired output, please suggest those as well.
As suggested by #Arkellys I have used one component per category & its worked for me. Thanks again #Arkellys.
I am adding answer below for more details:
import React, { useState } from 'react';
import FaqData from 'react-faq-component';
function Faq() {
const [rows, setRowsOption] = useState(null);
const data1 = {
title: 'FAQ (how it works)',
rows: [
{
title: 'How do I change my password?',
content: `Answer here.`,
},
{
title: 'How do I sign up?',
content:'Answer here.',
},
],
};
const data2 = {
title: 'FAQ (how it works)',
rows: [
{
title: 'How do I change my password?',
content: `Answer here.`,
},
{
title: 'How do I sign up?',
content:'Answer here.',
},
],
};
const data3 = {
title: 'FAQ (how it works)',
rows: [
{
title: 'How do I change my password?',
content: `Answer here.`,
},
{
title: 'How do I sign up?',
content:'Answer here.',
},
],
};
return (
<div>
<h2 className="section-title">My FAQ's</h2>
<div className="faq-style-wrapper">
<FaqData data={data1} getRowOptions={setRowsOption} />
<FaqData data={data2} getRowOptions={setRowsOption} />
<FaqData data={data3} getRowOptions={setRowsOption} />
</div>
</div>
);
}
Background info:
I'm using react and material-ui.
To keep the code clean, I populate menu items from a const array, like so:
const menuItems = [
{ label: "Home", path: "/home" },
{ label: "Accounts", path: "/accounts" },
{ label: "Organizations", path: "/organizations" },
];
Each item in the array is an object containing a label and a redirect path. I map over the items when rendering. Very basic.
Problem:
I would like to include a material-ui icon component in the menuItems array so the icon can be rendered next to the label. But I can't find a way to reference the icons by a name string
https://material-ui.com/components/material-icons/
Possible solutions:
put the icon component into a string:
{ label: "Accounts", path: "/accounts" }, icon: "<AccountBox/>"} but then I somehow need to evaluate the string into jsx. I don't know how.
Make a react functional component which renders a different icon depending on a prop, for example: <IconSwitch icon = {"accountIcon"} /> and hard-code different icons inside the RFC. Not pretty, but should work.
Punt and use different icons such as svg icons or font icons that can referenced by a name string.
Any suggestions on how to do this?
Thanks
Icon Font
You can use the Icon component. https://material-ui.com/components/icons/#icon-font-icons
To use an icon simply wrap the icon name (font ligature) with the Icon component, for example:
import Icon from '#material-ui/core/Icon';
<Icon>star</Icon>
https://codesandbox.io/s/material-demo-forked-sj66h?file=/demo.tsx
Assuming you set up your menu items with the appropriate icon ligatures:
const menuItems = [
{ label: "Home", path: "/home", icon: "home" },
{ label: "Accounts", path: "/accounts", icon: "account_circle" },
{ label: "Organizations", path: "/organizations", icon: "settings" }
];
Then you can map over them:
{menuItems.map(({ label, icon }) => {
return (
<span key={label}>
{label} <Icon>{icon}</Icon>
</span>
);
})}
SVG Icons
If you want to use SVG icons instead of basic icons, I'd recommend pulling only the SVG icons you plan to use in order to allow the icons you aren't using to be tree-shaken from the resulting bundle. The ability to tree shake is a good reason to use SVG icons over font icons.
import { Home, AccountCircle, Settings, AddCircle } from "#material-ui/icons";
If you want to allow user input of all icons or aren't aware ahead of time which icons will be displayed, you can import everything from #material-ui/icons as in Jonathan's answer.
If you aren't putting the list of icons into something that needs to be able to be stringified (i.e. Redux/sent through an API call) then you can just directly put the icons into the array and render them:
const menuItems: MenuItem[] = [
{ label: "Home", path: "/home", icon: <Home /> },
{ label: "Accounts", path: "/accounts", icon: <AccountCircle /> },
{ label: "Organizations", path: "/organizations", icon: <Settings /> }
];
// Rendering:
{menuItems.map(({ label, icon }) => {
return (
<span key={label}>
{label} {icon}
</span>
);
})}
If you are going to put the Icons somewhere that needs to be stringified, the above won't work, so I'd recommend putting the icons you want to use into an object to map them. That way you have a string to icon map.
Example: https://codesandbox.io/s/material-icons-svg-udcv3?file=/demo.tsx
import { Home, AccountCircle, Settings, AddCircle } from "#material-ui/icons";
const icons = {
Home,
AccountCircle,
Settings
};
In the case of the example above (i.e. rendering the icons from an array)
interface MenuItem {
label: string;
path: string;
icon: keyof typeof icons;
}
const menuItems: MenuItem[] = [
{ label: "Home", path: "/home", icon: "Home" },
{ label: "Accounts", path: "/accounts", icon: "AccountCircle" },
{ label: "Organizations", path: "/organizations", icon: "Settings" }
];
// Rendering:
{menuItems.map(({ label, icon }) => {
const Icon = icons[icon];
return (
<span key={label}>
{label} <Icon />
</span>
);
})}
You can import all from #material-ui/icons and than create an Icon component dynamically:
import React from 'react'
import * as icons from '#material-ui/icons'
interface MenuItem {
label: string,
icon: keyof typeof icons,
path: string
}
export function Menu() {
const menuItems: MenuItem[] = [
{ label: 'Home', path: './home', icon: 'Home' },
{ label: 'Accounts', path: './accounts', icon: 'AccountCircle' },
{ label: 'Organizations', path: './organizations', icon: 'Settings' }
]
return (
<>
{menuItems.map(menuItem => {
const Icon = icons[menuItem.icon]
return (
<span key={menuItem.path}>
{menuItem.label} <Icon />
</span>
)
})}
</>
)
}
// I have better way to avoid all of this other hustle .
// 1: Make Every icon in Array which is in Jsx from to simple name.
// Ex:
[
{ Name: "New", Icon: <HomeIcon /> },
{ Name: "JS Mastery", Icon: <CodeIcon /> },
{ Name: "Coding", Icon: <CodeIcon /> },
{ Name: "ReactJS", Icon: <CodeIcon /> },
{ Name: "NextJS", Icon: <CodeIcon /> },
]
to
[
({ Name: "New", Icon: HomeIcon },
{ Name: "JS Mastery", Icon: CodeIcon },
{ Name: "Coding", Icon: CodeIcon },
{ Name: "ReactJS", Icon: CodeIcon })
];
// 2: Remember Using Object keys name as capital ,here:- "Name , Icon" not "name , icon".
// 3: Now Simply use : -
{
categories.map(({ Name, Icon }) => (
<button key={Name}>
<span>{Name}</span>
<span> {<Icon/>} </span>
</button>
));
}
//use icon in this cleaver way
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.
How do I change the spacing between 2 nav groups within a Nav element. I have the following element:
<Nav
groups={[
{
links: [
{
key: "profile",
name: "View Profile",
url: "/account"
},
{
key: "manageCompanies",
name: "Manage Companies",
url: "/manageCompanies"
}
]
},
{
name: "Sale",
links: [
{
key: "Invoice",
name: "Invoice",
url: "/invoice"
},
{
key: "Quotation",
name: "Quotation",
url: "/quotation"
},
{
key: "Delivery Challan",
name: "Delivery Challan",
url: "/deliveryChallan"
}
]
},
{
name: "Purchase",
links: [
{
key: "Purchase Order",
name: "Purchase Order",
url: "/purchaseOrder"
}
]
}
]}
/>
I'm getting the following output:
How do I remove the empty space between each link group? I've tried styles prop, but I'm not able to get rid of the extra space.
Looks like there's a class called ms-Nav-groupContent on the nav groups with a margin-bottom of 40px. You can override that as long as your CSS is coming after the default styles. Example here.
If you don't want the changes to be globally applied to all nav groups, you can apply a className directly to the Nav component, and then use the CSS descendant combinator to select only the groups within that Nav, e.g.:
.my-class .ms-Nav-groupContent {
margin-bottom: 5px;
}
You can use the following styles prop:
styles={{
groupContent: {
marginBottom: "5px"
}
}}