Default layout in Gatsby MDX V4 - reactjs

I have recently updated Gatsby from V3 to V4(latest one) and also updated the plugins below.
"#mdx-js/mdx": "^2.1.3",
"#mdx-js/react": "^2.1.3",
"gatsby-plugin-mdx": "^4.1.0",
"gatsby": "^4.21.1",
The code in
node-config.js -
{
resolve: `gatsby-plugin-mdx`,
options: {
defaultLayouts: { default: require.resolve(`./src/components/layout`), },
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages/`,
},
},
In /src/pages/index.mdx
---
title: Title
description: Description
class: home
imageTwitter: /twitter-home.jpg
imageOg: "/og-home.jpg"
imageAlt: Image for the Title.
---
import { StaticImage } from "gatsby-plugin-image"
import { Container, Row, Col, Card } from "react-bootstrap"
import { Link } from "gatsby"
import { navigate } from "gatsby"
<div class="w-60 text-center home-intro">
content
</div>
Latest version of gatsby-plugin-mdx doesn't have defaultLayout so when I hit http://localhost:8000/ the page loads without header and footer because the layout doesn't work.
In /src/components/layout.js
import React from "react"
import PropTypes from "prop-types"
import { StaticQuery, graphql } from "gatsby"
import { Container, Row, Col } from "react-bootstrap"
import "../styles.scss"
import Footer from "./footer"
import Menu from "./menu"
import Helmet from "react-helmet"
const Layout = ({ location, children, pageContext, ...props }) => (
<StaticQuery
query={graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
siteUrl
}
}
allMdx {
edges {
node {
frontmatter {
title
description
class
imageOg
imageTwitter
}
}
}
}
}
`}
render={data => (
<>
<Helmet>
<body className={pageContext.frontmatter.class} />
</Helmet>
<Container>
<Row>
<Col>
<Menu />
</Col>
</Row>
</Container>
<Container className="full-width">
<Row className="mx-0">
<Col>
<main>
<article id="content">
{children}
</article>
</main>
</Col>
</Row>
</Container>
<Footer />
</>
)}
/>
)
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
Is there any way I can create a default template inside the page's directory and then reference each MDX file as a child? For example, if it's index page then it will select home.mdx
Any help is highly appreciated.

According to the migration guide, you have several options to use a common layout across dynamic page creation:
Use the wrapPageElement API including its SSR counterpart.
Add an export default Layout statement to your MDX file, see MDX documentation on Layout.
When using the createPage action to programatically create pages, you should use the following URI pattern for your page component: your-layout-component.js?__contentFilePath=absolute-path-to-your-mdx-file.mdx. To learn more about this, head to the programmatically creating pages guide.
Given your scenario, I'd say option 2 and 3 fits better since they are easy to implement and has fewer changes to conflict to whatever you had already but. Chose wisely, whatever fits your specs (if you are or ain't using gatsby-node.js, using global wrappers as Redux, etc.).
Option 2
If you define a Layout, it will be used to wrap all content. A layout can be defined from within MDX using a default export:
export default function Layout({children}) {
return <main>{children}</main>;
}
All the things.
Or:
export {Layout as default} from './components.js'
The layout can also be passed as components.wrapper (but a local one takes precedence).
Option 3
In your gatsby-node.js, in your page generation you need to point to the layout like:
createPage({
path: `/some-slug/`,
component: `layout-component.js?__contentFilePath=${absolute-path-to-your-mdx-file.mdx}`,
context: {
id: `123456`,
},
})
Note: this approach may force you to tweak a little bit the GraphQL query to add the needed data to build the __contentFilePath properly.

Related

Warning: Failed prop type: The prop `alt` is marked as required in `MainImage`, but its value is `undefined`. MainImage

In my gatsby project I am getting this warning.
I am using Gatsby v3.
The warning looks like this
Warning: Failed prop type: The prop `alt` is marked as required in `MainImage`, but its value is `undefined`.MainImage
The index.js looks like this.
import { graphql, Link } from "gatsby"
import React from "react"
import Layout from "../components/Layout"
import * as style from "../styles/home.module.css"
import { GatsbyImage } from "gatsby-plugin-image"
export default function Home({ data }) {
console.log(data)
return (
<Layout>
<section className={style.header}>
<div>
<h2>Design</h2>
<h3>Develop & deploy</h3>
<p>Become web ninja</p>
<Link to="/projects" className={style.btn}>
My Portfolio Projects
</Link>
</div>
<GatsbyImage image={data.file.childImageSharp.gatsbyImageData} />
</section>
</Layout>
)
}
export const query = graphql`
query Banner {
file(relativePath: { eq: "banner.png" }) {
childImageSharp {
gatsbyImageData(layout: FIXED)
}
}
}
`
Please let me know why I am getting this warning ?
Also how can I use fluid image in gatsby v3 ?
alt prop in the new <GatsbyImage> component, from the v3 of Gatsby, is required for accessibility improvements, so if you don't have it, just leave it empty but you need to provide it:
<GatsbyImage image={data.file.childImageSharp.gatsbyImageData} alt={``} />
Also how can I use fluid image in gatsby v3 ?
As you can see in the migration guide and in the docs, the fluid attribute has been renamed to FULL_WIDTH as a layout property, so to use it, the query should look like something like:
childImageSharp {
gatsbyImageData(layout: FULL_WIDTH)
}
Try adding image alt property, which cause warning in react
<GatsbyImage src="../images/dino.png" alt="A dinosaur" />
First update your graphQl request to include alt (presuming you have alt):
childImageSharp {
gatsbyImageData(layout: FIXED)
alt
}
Then update your markup
<GatsbyImage image={data.file.childImageSharp.gatsbyImageData} alt={data.file.childImageSharp.alt}/>

Basic Gatsby Shopify example missing Layouts

I am working with a basic Gatsby Shopify website template here https://www.gatsbyjs.com/docs/building-an-ecommerce-site-with-shopify/
I am trying to get this simple example up and running but I noticed in the following code block in
/src/pages/products.js
import Layout from "../components/layout"
there is no mention of components or layouts in the article and the app is throwing errors there. I am just trying to get a basic example to work. Is there a github link for this code?
The <Layout> component is a common resource in mostly all Gatsby starters (the default one for example), if you don't have it, just create the following structure under /components folder (to keep your code structure):
/**
* Layout component that queries for data
* with Gatsby's useStaticQuery component
*
* See: https://www.gatsbyjs.com/docs/use-static-query/
*/
import React from "react"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import Header from "./header"
import "./layout.css"
const Layout = ({ children }) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`)
return (
<>
<Header siteTitle={data.site.siteMetadata?.title || `Title`} />
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `0 1.0875rem 1.45rem`,
}}
>
<main>{children}</main>
<footer style={{
marginTop: `2rem`
}}>
© {new Date().getFullYear()}, Built with
{` `}
Gatsby
</footer>
</div>
</>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
As you can see, the <Layout> component wraps the whole application with the children prop, sharing a <Header> component and a <footer> tag across all the applications when used.
You can remove propTypes if you are not using them.

How do I pass a className to a image component in Gatsby

I am using gatsby-image and gatsby-source-filesytem I want the img tag (Logo component) when rendered as html to have a className of logo how do I go about doing this, gatsby-image docs say to pass it through props. I still don't quite understand react, so need help understanding here is my code.
logo.js
import React from "react"
import { StaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
/*
* This component is built using `gatsby-image` to automatically serve optimized
* images with lazy loading and reduced file sizes. The image is loaded using a
* `StaticQuery`, which allows us to load the image from directly within this
* component, rather than having to pass the image data down from pages.
*
* For more information, see the docs:
* - `gatsby-image`: https://gatsby.app/gatsby-image
* - `StaticQuery`: https://gatsby.app/staticquery
*/
const Image = () => (
<StaticQuery
query={graphql`
query {
placeholderImage: file(relativePath: { eq: "riel-type.png" }) {
childImageSharp {
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
}
`}
render={data => <Img fluid={data.placeholderImage.childImageSharp.fluid} />}
/>
)
export default Image
index.js
import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image"
import Logo from "../components/logo"
import SEO from "../components/seo"
const IndexPage = () => (
<Layout>
<SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
<div className="row">
<div className="col-4">
<Logo />
</div>
</div>
</Layout>
)
export default IndexPage
gatsby-image docs say to pass it through props
Means that you can add any property you want inside the Img tag. Because of some internal restriction you have to use the JavaScript name attribute and not the HTML one (ie className instead of class)
So :
render={data => <Img fluid={data.placeholderImage.childImageSharp.fluid} className="logo" />}

gatsbyjs, reactjs - why components are rendering twice and images do not appear?

I am new to gatsbyjs and using v2.
I've 3 components - loader, header and layout.
layout.js
import React from "react"
import Helmet from 'react-helmet'
import { StaticQuery, graphql } from 'gatsby'
import Header from "./header"
import Loader from "./loader"
import 'bootstrap/dist/css/bootstrap.min.css'
import "./layout.module.css"
const Layout = ({ children }) => (
<StaticQuery
query={graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
menuLinks {
name
link
}
}
}
}
`}
render={data => (
<React.Fragment>
<Helmet
title={'tite'}
meta={[
{ name: 'description', content: 'Sample' },
{ name: 'keywords', content: 'sample, something' },
]}
>
</Helmet>
<Loader />
<Header menuLinks={data.site.siteMetadata.menuLinks} siteTitle={data.site.siteMetadata.title} />
<div>{children}</div>
</React.Fragment>
)}
/>
)
export default Layout
In index.js
import React from 'react'
import Layout from "../components/layout"
export default () => (
<Layout>
</Layout>
)
Every component is being rendered twice as shown in screenshot.
Another issue I am facing with images. All the images are in src/images/ and when I use it as below in header component:
import React from "react"
import { Link } from 'gatsby'
import styles from "./layout.module.css"
const logo = 'src/images/logo.png';
const Header = ({ siteTitle, menuLinks }) => (
<header className={styles.header_area}>
<nav className={`${styles.navbar} navbar-expand-lg ${styles.menu_one} ${styles.menu_four}`}>
<div className="container">
<a className="navbar-brand sticky_logo" href="#">
<img src={logo} alt="logo" />
The image doesn't show up on a page. I checked Source in chrome developer tools and found that images are not being served via webpack.
So, why components render twice and why image doesn't show up ? what am I missing or doing worng here ?
I had the same issue when using the gatsby-plugin-layout plugin. The doc is not really clear, but when using the gatsby-plugin-layout plugin, you don't need to wrap your page between the Layout component. That plugin takes care of this automatically. If you explicitly wrap your JSX between the Layout component, the Layout is rendered twice.
I'm not sure why your page is loading double components, are you coming to the site directly, or from another path?
For your image not showing up, this is why:
Everything in your src folder is meant to be dynamic, meaning it won't be served directly. If you want to include image statically, you can create a public folder in your root directory (at the same level with src folder), and put images in there. Anything in this public folder will be served directly. So for example, you can have this structure:
|-- src
`-- public
`-- images
`-- logo.png
Then in your code, you can include the path like
<img src="/images/logo.png" alt="logo" />
I think for a logo like your use case, this method is sufficient. However, if you always link images like this, you'll be missing out a lot of gatsby's cool feature. For example, gatsby can load your image lazily, or optimize the file size! You can learn more here (gatsby official blog).

Initialize script in componentDidMount – runs every route change

I am working on a navbar for my react app (using gatsbyjs to be precise). In the navbar I have a marquee that I initialize in the navbar component in componentDidMount.
It works as intended, but upon every route change componentDidMount will run again which results in the marquee speeding up for every page change, making it go faster and faster.
Is this expected behaviour? And if so, how do I make sure that the script is only run once?
navbar.js
import React from 'react';
import { Link } from 'gatsby';
import styles from '../styles/navbar.module.css';
import NewsMarquee from './newsMarquee';
import Marquee3k from 'marquee3000';
const topLevelNav = [
{
href: '/news',
label: <NewsMarquee/>,
extraClass: styles.navLinkNews,
mediaQueryClass: styles.navLinkHiddenSmall,
},
];
export default class Navbar extends React.Component {
componentDidMount() {
Marquee3k.init();
}
render() {
return (
<div>
<header className={styles.navbar} role="banner">
<nav className={styles.nav}>
{topLevelNav.map(({ href, label, extraClass = '', mediaQueryClass = '' }) => (
<Link
key={label}
to={href}
className={`${styles.navLink} ${extraClass} ${mediaQueryClass} ${menuItemsHidden}`}
activeClassName={styles.navLinkActive}
>
{label}
</Link>
))}
</nav>
</header>
</div>
)
}
}
newsMarquee.js
import React from 'react';
import { StaticQuery, graphql } from "gatsby";
import styles from '../styles/newsMarquee.module.css';
export default () => (
<StaticQuery
query={graphql`
query {
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC } limit: 10) {
totalCount
edges {
node {
id
frontmatter {
title
date(formatString: "YYYY.MM.DD")
}
fields {
slug
}
}
}
}
}
`}
render={data => (
<div className={`marquee3k ${styles.marquee}`}>
<div>
{data.allMarkdownRemark.edges.map(({ node }) => (
<span className={styles.marqueeItem} key={node.id}>
{node.frontmatter.date} {node.frontmatter.title}
</span>
))}
</div>
</div>
)}
/>
)
Since I'm using GatsbyJS I went with this plugin from V1, which makes my layout component persist across pages.
gatsby-plugin-layout
This plugin enables adding components which live above the page components and persist across page changes.
This can be helpful for:
Persisting layout between page changes for e.g. animating navigation
Storing state when navigating pages
Custom error handling using componentDidCatch
Inject additional data into pages using React Context.
This plugin reimplements the behavior of layout components in gatsby#1, which was removed in version 2.

Resources