Accessing Current Page's Frontmatter -- Gatsby/React - javascript

Is it possible to access the current page's frontmatter through the default layout.
Using graphql, is there an option to filter a query based on the page's url?
.MDX Frontmatter
---
Title: 'About Us'
---
gatsby-config.js
{
resolve: 'gatsby-plugin-mdx',
options: {
defaultLayouts: {
pages: require.resolve('./src/layouts/default.js'),
},
},
}
default.js
<>
<header>?? Insert MDX Title ??</header>
<main></main>
<footer></footer>
</>

Even if you are generating pages from ./src/pages/ automatically using a default MDX template you still need to add the following to your plugins section in gatsby-config.js:
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages/`,
}
}
Now you can access the frontmatter with pageContext.frontmatter as mentioned in the documentation i.e.:
import React from 'react'
import Layout from './layout'
export default ({ children, pageContext }) => {
return (
<Layout>
<h1>{pageContext.frontmatter.title}</h1>
<article>{children}</article>
</Layout>
)
}

Yes there is. One way to step through it is to run gatsby develop and run your queries against the GraphiQL instance that gets ran. I have the title in my .mdx files and it is available through the allMdx and mdx schema. The Gatsby documentation here explains how to set up your component with the query.
{
allMdx {
edges {
node {
frontmatter {
title
}
}
}
}
mdx {
frontmatter {
title
}
}
}

MDX frontmatter is passed automatically along with children into the parent context.
{props.pageContext.frontmatter.Title}

Related

Fetch only one element from Graphql in Gatsby

am new to gatsby and graphql and I came across a tutorial where it is mentioned to fetch all the data using .map. But I want to fetch only one element from the DB. So how do I do it?
import React from "react";
import Layout from "../components/layout";
import { useStaticQuery, graphql, Link } from "gatsby";
const Blogs = () => {
const data = useStaticQuery(
graphql`
query {
allMarkdownRemark(sort: { frontmatter: { date: ASC } }) {
edges {
node {
frontmatter {
title
date(formatString: "DD MM, YYYY")
}
excerpt
id
fields {
slug
}
}
}
}
}
`
);
return (
<Layout>
<ul>
{data.allMarkdownRemark.edges.map((edge) => {
return (
<li key={edge.node.id}>
<h2>
<Link to={`/blog/${edge.node.fields.slug}/`}>
{edge.node.frontmatter.title}
</Link>
</h2>
<div>
<span>
Posted on {edge.node.frontmatter.date}
</span>
</div>
<p>{edge.node.excerpt}</p>
<div>
<Link to={`/blog/${edge.node.fields.slug}/`}>Read More</Link>
</div>
</li>
);
})}
</ul>
</Layout>
);
};
export default Blogs;
Lets say I have multiple blogs and I wish to show only a specific one in a page through query like...
query MyQuery {
markdownRemark((id: {eq: "9ac19d6d"}) //Some ID {
title
description
content
}
}
How to get this on a page to display?
Thanks in advance!
Depending on what do you want to achieve:
If you want just a specific single post. You can filter your useStaticQuery to add the value of the id (if you know it beforehand) like:
query MyQuery {
markdownRemark((id: {eq: "123"}) {
title
description
content
}
}
useStaticQuery as the name points out, is static and doesn't accept dynamic values.
Another alternative is to get a specific position from data.allMarkdownRemark to display it.
If you just want a single post without any filter you can take advantage of the GraphQL query options:
{
allMarkdownRemark(limit: 1) {
edges {
node {
frontmatter {
title
}
}
}
}
}
If you are trying to create dynamic posts, hence each post template will display a different blog post (one per template), you need to pass a filter value from gatsby-node.js (where you create the post pages) to the template through Gatsby's context:
// gatsby-node.js
posts.forEach(({ node }, index) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
id: node.id,
title: node.title
},
})
})
Note: here I'm lifting the id and the title. Use whatever works better for you
Now, you can take advantage of the context in your Blogs component (as long as it's a template):
const Blogs = ({data}) => {
console.log("your blog post is:", data)
return (
<Layout>
<h1>{data.markdownRemark.title}</h1>
</Layout>
);
};
export const query = graphql`
query($id: String!, $title: String!) {
markdownRemark((id: {eq: $id}) {
title
description
content
}
}
`
export default Blogs;
In other words: the first approach uses a static query (via useStaticQuery hook. Static, no dynamic parameters allowed) and the second uses a page query (only available in pages or templates)
With your query:
query MyQuery {
markdownRemark((id: {eq: "9ac19d6d"}) //Some ID {
title
description
content
}
}
Your data will be in data.markdownRemark
You can access those 3 fields directly.
const { title, description, content ] = data.markdownRemark;
return (
<Layout>
<div>
<p>{title}</p>
<p>{description]</p>
<p>{content}</p>
</div>
</Layout>
)

"Couldn't find story matching 'example-api--primary' after HMR" error in Storybook

I try to configure Storybook in Svelte project.
I want to create a section with only "Docs" tab.
But if I add in parameters "docs" section (no metter whats inside), I get a error:
Couldn't find story matching 'example-api--primary' after HMR.
- Did you remove it from your CSF file?
- Are you sure a story with that id exists?
- Please check your stories field of your main.js config.
- Also check the browser console and terminal for error messages.
In console:
Unable to load story 'example-api--primary'
almost all code bellow I take from StoryBook site.
My story file:
Api.stories.js
import Button from '../../stories/Button.stories';
export default {
title: 'Example/API',
component: Button,
parameters: {
previewTabs: {
canvas: { hidden: true},
},
viewMode: 'docs',
docs: {
page: ApiStories,
},
},
};
stories/introduction#using-args
const Template = (args) => ({
Component: Button,
props: args,
});
export const Primary = Template.bind({});
Primary.args = {
};
My ApiStories.mdx file:
<!-- Custom-MDX-Documentation.mdx -->
# Replacing DocsPage with custom `MDX` content
This file is a documentation-only `MDX`file to customize Storybook's [DocsPage](https://storybook.js.org/docs/react/writing-docs/docs-page#replacing-docspage).
It can be further expanded with your own code snippets and include specific information related to your stories.
For example:
import { Story } from "#storybook/addon-docs";
## Button
Button is the primary component. It has four possible states.
- [Primary](#primary)
- [Secondary](#secondary)
- [Large](#large)
- [Small](#small)
## With the story title defined
If you included the title in the story's default export, use this approach.
### Primary
<Story id="example-button--primary" />
### Secondary
<Story id="example-button--secondary" />
### Large
<Story id="example-button--large" />
### Small
<Story id="example-button--small" />
## Without the story title defined
If you didn't include the title in the story's default export, use this approach.
### Primary
<Story id="your-directory-button--primary"/>
### Secondary
<Story id="your-directory-button--secondary"/>
### Large
<Story id="your-directory-button--large"/>
### Small
<Story id="your-directory-button--small" />
My config file:
.storybook/main.js
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.#(js|jsx|ts|tsx|svelte)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
"#storybook/addon-interactions"
],
"framework": "#storybook/svelte",
"svelteOptions": {
"preprocess": require("svelte-preprocess")()
}
}
How to add something in Docs tab correctly?

Dynamically rendered images in Gatsby

I'm working on the blog based on React, TS and Gatsby.
Blog posts are based on markdown. Each blog post will have a similar header with title, time necessary to read the article and the logos of the technologies that the particular blog post will be about.
I have a problem with rendering those images dynamically. My idea was to create something like this in markdown
---
path: "/fourth"
date: "2021-06-02"
title: "TypeScript - intro"
readTime: "140"
author: "Adam Kniec"
imgs: [typescript, react]
---
That's the fourth blog post
after that I wanted to create a graphql query and get the imgs names like so:
export const postQuery = graphql`
query BlogPostByPath($path: String!) {
markdownRemark(frontmatter: { path: { eq: $path } }) {
html
frontmatter {
path
readTime
title
author
imgs
date
}
}
}
`;
I have the array of images in the props now and I wanted to render those images like this
{data.markdownRemark.frontmatter.imgs.map((imgPath) => {
const imgPatha = `../images/${imgPath}`;
return <StaticImage src={imgPatha} alt="asdasd" />;
})}
Unfortunately, gatsby gives me the warning
react_devtools_backend.js:2560 Image not loaded ../images/typescript
Is this the correct approach ? Please let me know what I'm doing wrong or how to render those images dynamically.
As it has been said by #coreyward you can't use dynamic props in the StaticImage component, it's a known restriction.
That said, you have two choices:
Using the standard img HTML tag.
Using the GatsbyImage component. To do it, you'll need to add the images in your filesystem to allow Gatsby to create the proper nodes and then, you will need to query them in your pages/templates. Without further details on the implementation, it's impossible to guess how your code should look like but the idea relies on something like:
import { graphql } from "gatsby"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
function BlogPost({ data }) {
const image = getImage(data.blogPost.avatar)
return (
<section>
<h2>{data.blogPost.title}</h2>
<GatsbyImage image={image} alt={data.blogPost.author} />
<p>{data.blogPost.body}</p>
</section>
)
}
export const pageQuery = graphql`
query {
blogPost(id: { eq: $Id }) {
title
body
author
avatar {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
}
`

Vue - Pass components to router-view on dynamically loaded component

I need to render a different layout for the same route for a specific URI with different components depending on the user being on mobile or in desktop.
I would like to avoid having route path checks in the PageCommon(layout component) to keep it clean.
The app has a main component taking care of the layout, it has different router-views where we load the different components for each page URI. This would be a normal route for that.
{
path: '',
component: PageCommon,
children: [
{
path: '',
name: 'Home',
components: {
default: Home,
header: Header,
'main-menu': MainMenu,
'page-content': PageContent,
footer: Footer,
'content-footer': ContentFooter
}
},
I can't change the route components property once the component is loaded so I tried to make a wrapper and pass the components dynamically.
{
path: 'my-view',
name: 'My_View',
component: () => import('#/components/MyView/ViewWrapper')
},
In /components/MyView/ViewWrapper'
<page-common v-if="isMobile">
<my-mobile-view is="default"></my-mobile-view>
<main-menu is="main-menu"></main-menu>
</page-common>
<page-common v-else>
<my-desktop-view is="default"></my-desktop-view>
<header is="header"></header>
<main-menu is="main-menu"></main-menu>
<footer is="footer"></footer>
</page-common>
</template>
I would expect that the components passed inside page-common block would be substituted on the appropriate , but is not how it works, and Vue just loads page-common component with empty router-views.
Is there any approach for this?
Note that I already tried using :is property for loading different components, but the problem then is on how to tell the parent to use this or that component for this page. This is the code for that:
<template>
<component :is="myView"></component>
</template>
<script>
import DesktopView from "#/components/MyView/DesktopView";
import MobileView from "#/components/MyView/MobileView";
export default {
name: 'MyView',
components: {
DesktopView,
MobileView,
},
data(){
return {
myView: null,
isMobile: this.detectMobile()
}
},
methods : {
getViewComponent() {
return this.isMobile ? 'mobile-view' : 'desktop-view';
}
},
created() {
this.myView = this.getViewComponent();
}
}
</script>
I could use this approach for each of the PageCommon router views, creating a component for each that does the above, but it looks like a very bad solution.
A computed method is all you need.
You should have this top level Logic in App.vue and the <router-view> should be placed in both DesktopView and MobileView.
// App.vue
<template>
<component :is="myView"></component>
</template>
<script>
import DesktopView from "#/components/MyView/DesktopView";
import MobileView from "#/components/MyView/MobileView";
export default {
name: 'MyView',
components: {
DesktopView,
MobileView,
},
computed: {
myView() {
return this.detectMobile() ? 'mobile-view' : 'desktop-view';
}
}
}
</script>
You may also want to consider code splitting by setting up Dynamic Components for those layouts since Mobile will load Desktop View because it is compiled into final build, register them globally as dynamic imports instead if importing them in MyView and then delete components also after doing the following instead, this way only the one that is needed will be downloaded saving mobile users their bandwidth:
// main.js
import LoadingDesktopComponent from '#/components/LoadingDesktopComponent '
Vue.componenet('desktop-view', () => ({
component: import('#/components/MyView/DesktopView'),
loading: LoadingDesktopComponent // Displayed while loading the Desktop View
})
// LoadingDesktopComponent .vue
<template>
<div>
Optimizing for Desktop, Please wait.
</div>
</template>
<script>
export default {
name: 'loading-component'
}
</script>
Routing logic will only be processed when <router-view> is available,this means you can delay the presentation of Vue Router, for example you can have :is show a splash screen like a loading screen on any URI before displaying a component in :is that contains <router-view>, only than at that point will the URI be processed to display the relevant content.

How can I use the async function for my head object in Vue js?

I have to display dynamic meta descriptions for my articles and I am kind of struggling to achieve that with the async function for my head object. This is what I have so far:
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
#Component
export default class ArticleContent extends Vue {
article: any | null = null;
articlelist = example.data;
async asyncData({params: any }) { <----- Not sure how I could put in my articlelist here
return this.articlelist;
}
head(): object {
return {
title: this.articlelist.productId.productNames['en'],
meta: [
{
hid: this.articlelist._id,
name: this.articlelist.productNames['en'],
content: this.articlelist.metaDescription['en'],
},
],
};
}
}
</script>
articlelist is what I am using in the head() object for my meta description. Would appreciate some help!
Both the head() and asyncData() properties are not part of the core of vue,
to use head() you need to install this as a plugin
to use asyncData() you have to use nuxt
If your spa has a strong need for seo I suggest you use nuxt, which natively includes seo and the conversion from vue to nuxt is very easy
If you already using nuxt this is the correct way to get
async asyncData({params: any }) {
const articlelist = await axios.get('some.url'); //get the data
return { articlelist }; //this object is merged with the data of the istance
}

Categories