I'm making some changes to a Gatsby starter and ran into an annoying problem with fontawesome brand icons. The problem is that unlike most icons, that are called faPhone or something like that, the brand icons also need a prefix ,fab.
So I can make them work in the body of my posts, like this: <Icon icon={['fab', 'github']} />
The problem is when I wanna include it on the frontmatter metadata like:
---
category: 'socials'
title: 'Github'
icon: 'fab github'
content: 'janedoegithub'
---
I have tried passing it like the above example, as 2 separate strings, as a list, and nothing seems to work. Does anyone know how to solve this?
Edit: both of these formats also don't work
---
category: 'socials'
title: 'Github'
icon:
- fab
- github
content: 'janedoegithub'
---
---
category: 'socials'
title: 'Github'
icon: ['fab', 'github']
content: 'janedoegithub'
---
when I try them I get this error:
GRAPHQL
There was an error in your GraphQL query:
Cannot query field "icon" on type "MdxFrontmatter".
If you don't expect "icon" to exist on the type "MdxFrontmatter" it is most
likely a typo.
However, if you expect "icon" to exist there are a couple of solutions to common
problems:
- If you added a new data source and/or changed something inside
gatsby-node.js/gatsby-config.js, please try a restart of your development server
- The field might be accessible in another subfield, please try your query in
GraphiQL and use the GraphiQL explorer to see which fields you can query and
what shape they have
- You want to optionally use your field "icon" and right now it is not used
anywhere. Therefore Gatsby can't infer the type and add it to the GraphQL
schema. A quick fix is to add at least one entry with that field ("dummy
content")
It is recommended to explicitly type your GraphQL schema if you want to use
optional fields. This way you don't have to add the mentioned "dummy content".
Visit our docs to learn how you can define the schema for "MdxFrontmatter":
https://www.gatsbyjs.org/docs/schema-customization/#creating-type-definitions
File: src/components/ContactInfo/index.js:25:15
this is the index.js mentioned in the error:
import React from 'react';
import { useStaticQuery, graphql } from 'gatsby';
import InfoBlock from 'components/ui/InfoBlock';
import Container from 'components/ui/Container';
import TitleSection from 'components/ui/TitleSection';
import * as Styled from './styles';
const ConctactInfo = () => {
const { mdx, allMdx } = useStaticQuery(graphql`
query {
mdx(frontmatter: { category: { eq: "socials section" } }) {
frontmatter {
title
subtitle
}
}
allMdx(filter: { frontmatter: { category: { eq: "socials" } } }, sort: { fields: fileAbsolutePath }) {
edges {
node {
id
frontmatter {
title
icon
content
}
}
}
}
}
`);
const sectionTitle = mdx.frontmatter;
const socials = allMdx.edges;
return (
<Container section>
<TitleSection title={sectionTitle.title} subtitle={sectionTitle.subtitle} center />
{socials.map((item) => {
const {
id,
frontmatter: { title, icon, content }
} = item.node;
return (
<Styled.ContactInfoItem key={id}>
<InfoBlock icon={icon} title={title} content={content} center />
</Styled.ContactInfoItem>
);
})}
</Container>
);
};
export default ConctactInfo;
As far as I understand your issue, you want to pass the icon data field from the markdown to the template/component. In that case, you can use:
---
category: 'socials'
title: 'Github'
icon:
- fab
- github
content: 'janedoegithub'
---
Note: be careful with the indentation
Or:
---
category: 'socials'
title: 'Github'
icon: ['fab', 'github']
content: 'janedoegithub'
---
You can store arrays in markdown files both ways.
Then, once your GraphQL query have retrieved and filtered your results, your component may look like:
<Icon icon={markdownData.icon} />
Related
I read the documentation and tried several tutorials, but I am stuck on fetching custom post types with GatsbyJS.
I tried several approaches, but none of them are working as expected. I always receive a 404.
This is a part of the snippet I am using, which works fine with pages and posts, but not with a custom post type.
The projects pages should be created under a project subfolder/path. Like: example.com/project/my-first-project
The part of the gatsby-node.js looks like that:
const createSinglePages = async ({ posts, gatsbyUtilities }) =>
Promise.all(
posts.map(({ previous, post, next }) =>
// createPage is an action passed to createPages
// See https://www.gatsbyjs.com/docs/actions#createPage for more info
gatsbyUtilities.actions.createPage({
// Use the WordPress uri as the Gatsby page path
// This is a good idea so that internal links and menus work 👍
path: post.uri,
// use the blog post template as the page component
component: path.resolve(
`./src/templates/${post.__typename.replace(`Wp`, ``)}.js`
),
// `context` is available in the template as a prop and
// as a variable in GraphQL.
context: {
// we need to add the post id here
// so our blog post template knows which blog post
// the current page is (when you open it in a browser)
id: post.id,
// We also use the next and previous id's to query them and add links!
previousPostId: previous ? previous.id : null,
nextPostId: next ? next.id : null,
},
})
)
);
The src/template/project.js file looks like this:
import React from "react";
import { Link, graphql } from "gatsby";
import Image from "gatsby-image";
import parse from "html-react-parser";
import Layout from "../components/Layout";
import Seo from "../components/Seo";
const ProjectTemplate = ({ data: { post } }) => {
const featuredImage = {
fluid: post.featuredImage?.node?.localFile?.childImageSharp?.fluid,
alt: post.featuredImage?.node?.alt || ``,
};
return (
<Layout>
<Seo title={post.title} description={post.excerpt} />
<article
className="blog-post"
itemScope
itemType="http://schema.org/Article"
>
<header>
<h1 itemProp="headline">{parse(post.title)}</h1>
<p>{post.date}</p>
{/* if we have a featured image for this post let's display it */}
{featuredImage?.fluid && (
<Image
fluid={featuredImage.fluid}
alt={featuredImage.alt}
style={{ marginBottom: 50 }}
/>
)}
</header>
{!!post.content && (
<section itemProp="articleBody">{parse(post.content)}</section>
)}
</article>
</Layout>
);
};
export default ProjectTemplate;
export const pageQuery = graphql`
query ProjectById(
# these variables are passed in via createPage.pageContext in gatsby-node.js
$id: String!
) {
# selecting the current post by id
post: wpProject(id: { eq: $id }) {
id
content
title
date(formatString: "MMMM DD, YYYY")
featuredImage {
node {
altText
localFile {
childImageSharp {
fluid(maxWidth: 1000, quality: 100) {
...GatsbyImageSharpFluid_tracedSVG
}
}
}
}
}
}
}
`;
Is the Gatsby API creating a subfolder automatically, or do I need to define that somewhere for each post type?
Any help appreciated!
You define the "subfolder" under the path field in:
gatsbyUtilities.actions.createPage({
path: post.uri,
component: path.resolve(
`./src/templates/${post.__typename.replace(`Wp`, ``)}.js`
),
context: {
id: post.id,
previousPostId: previous ? previous.id : null,
nextPostId: next ? next.id : null,
},
})
You just need to do something like:
path: `projects/${post.id}`
Check the slashes and trailing slashes here.
You cna replace projects for your dynamic project type if you fetch that information for a more automatic approach (assuming it's post.__typename).
In order to use Custom Post Types with WPGraphQL, you must configure the Post Type to show_in_graphql using the following field:
show_in_graphql : true
While Registering a new Custom Post Type
This is an example of registering a new "docs" post_type and enabling GraphQL Support.
add_action( 'init', function() {
register_post_type( 'docs', [
'show_ui' => true,
'labels' => [
//#see https://developer.wordpress.org/themes/functionality/internationalization/
'menu_name' => __( 'Docs', 'your-textdomain' ),
],
'show_in_graphql' => true,
'hierarchical' => true,
'graphql_single_name' => 'document',
'graphql_plural_name' => 'documents',
] );
} );
Pretty much follow this post Adding tags to Gatsby Contentful blog to a T but still can't find a reason why it's not working...
import React from "react"
import { graphql } from "gatsby"
export const query = graphql`
query($slug: String!, $tag: [String!]) {
contentfulBrunchPost(slug: { eq: $slug }, tag: { in: $tag }) {
title
slug
tag
}
}
`
const TagPost = props => {
return <h1>{props.data.contentfulBrunchPost.title}</h1>
}
export default TagPost
I get the error "Cannot read property 'contentfulBrunchPost' of undefined". Very new to graphiQL in contentful and gatsby so point in the right direction would be awesome
I suggest going to your local version of Graphiql at http://localhost:8000/___graphql and copying your exact query over. Does the query return data over there? If not, then your query may be incorrect. If it does return data, then you can drill down into it to determine the correct reference to use in your code.
Extending from:
appreciate the help! I get the error "message": "Variable "$slug" of
required type "String!" was not provided." any point in right
direction? –
You've set the slug field as non-nullable (like required field) because of the exclamation mark (!). This means that must be provided from the gatsby-node.js file to the template (TagPost) so ensure that the field is always present or remove the nullability of the field to make it nullable:
export const query = graphql`
query($slug: String, $tag: [String!]) {
contentfulBrunchPost(slug: { eq: $slug }, tag: { in: $tag }) {
title
slug
tag
}
}
Your createPage function should look like:
createPage({
path: `/tag/${slugifiedTag}`,
component: path.resolve("./src/templates/tag-post.tsx"), // your tagComponent
context: {
slug: edge.node.slug,
tag: edges.node.tag
},
})
I have two markdown collection routes which I want to apply to two different sets of markdowns separated by subfolders.
My folder structure is as follows
appfolder
content
projects
project1.md
project2.md
article
article1.md
article2.md
src
pages
projects
{MarkdownRemark.frontmatter__slug}.js
articles
{MarkdownRemark.frontmatter__slug}.js
The content of projects/{MarkdownRemark.frontmatter__slug}.js is as follows
import React from 'react';
import Layout from "../../components/Layout";
import Nav from "../../components/Nav";
import PageHeader from "../../components/PageHeader";
import Footer from "../../components/Footer";
import SimpleReactLightbox from 'simple-react-lightbox'
import { graphql } from 'gatsby'
const ProjectPage = ({data}) => {
const fm = data.markdownRemark.frontmatter;
const html = data.markdownRemark.html;
return (
<SimpleReactLightbox>
<Layout pageTitle={fm.title}>
<Nav />
<PageHeader title={fm.title} />
<Footer />
</Layout>
</SimpleReactLightbox>
);
};
export const query = graphql`
query($id: String!) {
markdownRemark(id: { eq: $id },fileAbsolutePath: {regex: "/(projects)/" }) {
html
frontmatter {
slug
title
summary
icon
}
}
}
`
export default ProjectPage;
But GraphiQL shows that the pages are generated for all md files. How do I restrict each collection route to respective subfolder.
Think your issues falls at fileAbsolutePath for the regex.
Change (projects) to just projects
In that cases, I think the easiest approach is adding a "key" value in your frontmatter to make the filter. For example:
---
key: article
title: Test Title
anotherField: Another field value
---
The body of the markdown
Note: change key: article for key: projects when needed
Then, in your GraphQL query use the filter with something like:
export const query = graphql`
query($id: String!) {
markdownRemark(id: { eq: $id }, filter: { frontmatter: { key: { eq: "article" },
}
},, ) {
html
frontmatter {
slug
title
summary
icon
}
}
}
`
Tweak it as you need and check the availability of the filters in the localhost:8000/___graphql playground.
I just ran into the same problem. It seems like there are no ready-made solutions for your current organization of src/pages. But after running through the discussions on Gatsby, I found there are some workarounds, though I think it's a bit inconvenient.
Like this comment said:
If your source nodes contain something like category: "article" | "blog", you could generate separate routes with file nested in
category directory like /{SourceNode.category}/{SourceNode.slug}.tsx.
If your source node does not contain such a category field, then you
can append it to the nodes in onCreateNode hook, or create entirely
new nodes for your purposes
In your case, if you can add a category field to the frontmatter in your .md files, you can change your organization of src to
appfolder
content
projects
project1.md
project2.md
article
article1.md
article2.md
src
templates
project-template.js
article-template.js
pages
{MarkdownRemark.frontmatter__category}
{MarkdownRemark.frontmatter__slug}.js
Then projects/{MarkdownRemark.frontmatter__slug}.js goes to templates/project-template.js and the article one is similar.
Now in your {MarkdownRemark.frontmatter__slug}.js file, you can forward the data queried by GraphQL to the corresponding template.
// {MarkdownRemark.frontmatter__slug}.js
const GeneratedPage = ({ data }) => {
const templates = {
project: <ProjectTemplate data={data} />,
article: <ArticleTemplate data={data} />,
};
return templates[data.MarkdownRemark.frontmatter.category];
};
If it's not possible to add the category field into the .md file, make use of the createNodeField API to programmatically add it to MarkdownRemark nodes in gatsby-node.js. It maybe something like this:
// gatsby-node.js
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
if (node.internal.type === `MarkdownRemark`) {
const value = ...; // something mapping the directory of node to certain catecory
createNodeField({
name: 'category',
node,
value,
});
}
};
Hope this can be helpful to you.
So I've written a blog site in Gatsby and Remark. I've structured my posts like this:
Library/
-- category-name/
---- article-name/
------ index.md
This has worked really well and results in me being able to make paths like /category-name/article-name.
What I also would like to do is to be able to drop an image in there called 'hero.jpg' and for it to be automatically picked up by Gatsby without having to add a frontmatter reference to it.
I've managed to get so far by adding the following to 'gatsby-node.js':
const hero = (fs.existsSync(path.resolve(__dirname, `src/library/pages/${slug}hero.jpg`))) ? `${slug}hero.jpg` : ''
createNodeField({
node,
name: 'hero',
value: hero,
})
This works as far as the graphql data goes and I now see the following:
{
"node": {
"id": "3190922a-2207-5621-a7be-e02be9e8da24",
"fields": {
"hero": "/category-name/article-name/hero.jpg"
},
},
However on the actual page, the image link /category-name/article-name/hero.jpg doesn't exist so I get a dead image. I know this is because my image path is being transformed by gatsby-transformer-sharp but I don't know how to work out what it is being transformed to.
I believe I need to do something akin to the answers on this SO question but that seems to expect you to know that the relativePath is at the time you are writing your query but I won't have that information until after the query has returned the first time.
OnCreateNode hook added for claarity
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
// Add slug to MarkdownRemark node
if (node.internal.type === 'MarkdownRemark') {
const slug = createFilePath({ node, getNode, basePath: 'library' })
const hero = (fs.existsSync(path.resolve(__dirname, `src/library/pages/${slug}hero.jpg`))) ? './hero.jpg' : ''
createNodeField({
node,
name: 'slug',
value: slug,
})
createNodeField({
node,
name: 'hero',
value: hero,
})
}
}
I realized my previous answer was incorrect & overly complicated (It relies on node creation order, also there's no need to add fields to imageSharp nodes. Here's the link if someone's interested.). Here's the better answer:
Querying image of hero name in the same folder with mardown
Since the hero image is always at the same directory as the markdown file, we can simply query it based on its directory.
dir name ext
┌────────────┴────────────┐ ┌─┴─┐ ┌─┴─┐
absolute/path/to/directory/ hero .png
absolute/path/to/directory/ index .md
Graphql query:
file ( dir: { eq: "absolute/path/to/directory" }, name: { "hero" } ) {
childImageSharp {
fixed {
src
}
}
}
The only modification you need to make to your gatsby-node.js is to add this dir field to your page's context so we can use it as a variable.
We can get this dir by doing path.parse(node.fileAbsolutePath).dir, or get the dir field from remark's parent node getNode(node.parent.id).dir
+ const { dir } = getNode(node.parent.id)
createPage({
path: node.fields.slug,
component,
context: {
+ dir,
slug: node.fields.slug,
},
})
And query it like so:
export const pageQuery = graphql`
- query ArticleByPath($slug: String!) {
+ query ArticleByPath($slug: String!, $dir: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
id
htmlAst
frontmatter {
title
}
}
+ file (dir: { eq: $dir }, name: { eq: "hero" }) {
+ childImageSharp {
+ fixed {
+ src
+ }
+ }
+ }
}
`
And use it:
export default function Template({ data }) {
const post = data.markdownRemark
+ const hero = data.file ? data.file.childImageSharp : null
return (
<div className="landing-page-container">
<Helmet title={`Your Blog Name - ${post.frontmatter.title}`} />
<div className="blog-post">
+ {hero && <img src={hero.fixed.src} alt={post.frontmatter.title} />}
<h1>{post.frontmatter.title}</h1>
<div className="blog-post-content">{renderAst(post.htmlAst)}</div>
</div>
</div>
)
}
Here's the gist for article.js and gatsby-node.js.
i have a problem with yahoo/react-intl thats i want to make messages in string type but when im using FormattedMessage it gives me message wrapped in span and thats not cool.
i tried formatMessage and that not working too.
i be very thankful for any help or advise this is my code:
import React from 'react';
import {FormattedMessage} from 'react-intl';
export default {
items: [
{
name: <FormattedMessage id='app.dashboard'/>,
url: '/dashboard',
icon: 'icon-speedometer',
badge: {
variant: 'info',
text: 'New',
},
},
{
title: true,
name: <FormattedMessage id='app.dashboard'/>,
// optional wrapper object
wrapper: {
// required valid HTML5 element tag
element: 'strong',
// optional valid JS object with JS API naming ex: { className: "my-class", style: { fontFamily: "Verdana" }, id: "my-id"}
attributes: {},
},
// optional class names space delimited list for title item ex: "text-center"
class: '',`enter code here`
},
for use in jsx:
it's rendered as a <span>:
<FormattedMessage id='app.dashboard'/>
it's rendered as an <option>:
<FormattedMessage id='app.dashboard' children={msg=> <option>{msg}</option>}/>
or:
<FormattedMessage id='app.dashboard' tagName="option"/>
it's rendered to nothing:
<FormattedMessage id='app.dashboard' children={msg=> <>{msg}</>}/>
or:
<FormattedMessage id="app.dashboard">{txt => txt}</FormattedMessage>
To use it in a component, you can use formatMessage() like this:
const App=()=>{
const value = intl.formatMessage({ id: 'header.nav.login' });
return(<div>{value}</>)
}
Given that you inject the intl context by yourself, Then you can use the formatMessage function.
For example, in your case:
items: [
{
name: intl.formatMessage({id:'app.dashboard'});
}
]
To get intl in your component you have two choices:
get it from your component's context
use injectIntl to get it in your props.
If you're not in a component, it gets slightly harder but I would just put the id instead of the formatted message in name and then use the react-intl context when available. Here, in the component that consumes and displays this list of items.
The solution here is to upgrade react-intl to version 3.
In version 3, the <FormattedMesage> (and similarly others react-intl components) is rendering into React.Fragment.
If you want to render it to something else you can specify textComponent prop on IntlProvider, eg.:
<IntlProvider textComponent="span" />
See info in Migration Guide (v2 -> v3).