Importing Markdown file into React component in Gatsby - reactjs

In Gatsby, how can I display the contents of a markdown file within a React component?
I am using the official gatsby-starter-blog starter which comes with markdown support. It works great for the blog posts that are already set up.
However, I now added a new About page. And on that page, rather than writing all of the content in jsx, I would like to write some of the content in a separate markdown file that I can import into the React component for my About page.
What do I need to do to achieve this?
Folder structure:
content
- pages
-- about-content.md
src
- pages
-- about-page.js
PS: I do not want my md file turned into its own page. I want to import it into a React component. And I want that component to be the page.

In gatsby-starter-blog this is the code snippet that turns the markdown text into a blog page:
<section dangerouslySetInnerHTML={{ __html: post.html }} />
You take the GraphQL query and the code snippet from above and put it into the about-page.js component:
const About = (props) => {
const { data: { legal: { edges } } } = props;
const { node: { childMarkdownRemark: { frontmatter, html } } } = edges[0];
return (
<Layout>
// display other React components
<section dangerouslySetInnerHTML={{ __html: html }} /> // display markdown
// display other React comonents
</Layout>
);
}
export const query = graphql`
{
legal: allFile(
filter: {
internal: {
mediaType: {eq: "text/markdown"}},
name: {eq: "about-content"}
edges {
node {
childMarkdownRemark {
html
}
}
}
}
}
`;
export default About;
Note that Gatsby implicitly builds urls out of your file names inside the pages folder. So the url would be localhost:8000/about-page/.

Related

Gatsby dynamic routes: 404 instead of component, can't configure

I made a simple site with Gatsby.js and can't configure dynamic routes.
I have index.js page (was automatically created by react), that looks like:
import * as React from 'react'
const IndexPage = () => {
return (
<Layout
pageTitle="Home Page"
>
Some text for my main page
</Layout>
)
}
export const Head = () => <title>Home Page</title>
export default IndexPage
Layout components includes Header that looks like:
import React, { useState, useEffect } from 'react';
import { Link } from 'gatsby';
const Header = () => {
return (
<Wrapper style={{ *some styles* }}>
<Link to="/">Index</Link>
<Link to="/projects">Projects</Link >
</Wrapper>
)
};
export default Header;
I have my Projects page that looks like this:
import * as React from 'react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import Layout from '../layout'
const Projects = () => {
return (
<BrowserRouter>
<Layout>
<Wrapper>
<Routes>
<Route path="projects/:projectID/" component={ProjectDetails} />
</Routes>
<MyProjectLink to="/projects/1">
Project 1
</MyProjectLink>
<MyProjectLink to="/projects/2">
Project 2
</MyProjectLink>
</Wrapper>
</Layout>
</BrowserRouter>
)
}
export const Head = () => <title>Our Projects</title>
export default Projects
And I have my ProjectDetails component:
import React from 'react';
import { useParams } from 'react-router';
import Layout from '../../pages/layout';
const ProjectDetails = () => {
const { projectID } = useParams();
return (
<Layout>
<Wrapper>
<h2>Project {projectID}</h2>
</Wrapper>
</Layout>
);
}
export default ProjectDetails;
The problem is that when I navigate to localhost:8000/projects/1 (or "2", or "3", or "100500") I see a 404 page instead of my ProjectDetails component.
I've tried wrapping the index page to BrowserRouter and move the routes with my route there, but that's a dumb idea in my opinion (and it doesnt work).
Did I miss something? Some features of Gatsby (v5) that I don't know about? I'm new to Getsby and to be honest I thought that dynamic routes here work the same way as in React Router.
Gatsby extends its routing from React, however, the way to create routes is slightly different.
As far as I understand your code, you are trying to create a template page for projects: this can be simply done by creating a file inside /templates folder. A simple component like this should work:
const Projects = ({ data }) => {
return (
<Layout>
<Wrapper></Wrapper>
</Layout>
);
};
export const Head = () => <title>Our Projects</title>
export default Projects
This template, as long as you use it when creating pages (using either gatsby-node.js or File System Route API) will be used to hold each specific project data.
Each project data will be queried using GraphQL and held inside props.data but without knowing your source (can be markdown, JSON, CMS, etc) I can't provide a sample query.
Once Gatsby infers its GraphQL nodes from your data source, you can use it to get all projects, a specific project, or any other GraphQL data you need on any page/template (page query) or even using static queries.
The idea should be similar to:
// gatsby-node.js
projects.forEach(({ node }, index) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/project.js`),
context: {
title: node.title,
},
})
})
In your gatsby-node.js (or File System Route API) you get all projects, loop through them and createPage for each project. The path (URL) for each project will be the slug field (node.fields.slug) but you can use whatever you want. Gatsby will create dynamic pages based on this field.
Then you decide which component will be used as a template: path.resolve(`./src/templates/project.js`) in this case and finally, you populate the context to add a unique value (title in this case: again, this can be an id, the slug, etc). This value will be used to filter the node in the template.
In your Project template:
export const query = graphql`
query ($title: String) {
mdx(title: {eq: $title}) {
id
title
}
}
`
In this case, I'm using markdown-based sources (that's why the mdx node) and this node is filtered by the title ($title) using the context value. The data will be inside props.data of the template. Again, if you want to fetch all projects you will have available an allMarkdown or allMdx (or allJSON...) depending on the source node)

How to get file list in a directory with webpack 5 at compile time

The question is clear but I would like to give my use case for better understanding.
I'm creating a personal blog app and my blog posts will in .mdx format. These blog post .mdx files reside in a directory in the code base called posts. I can render any .mdx file in the following BlogPost component for each url like /posts/post1.
import React, { Suspense } from 'react'
import { useParams } from 'react-router-dom'
export const BlogPost = () => {
let params = useParams()
const Post = React.lazy(() => import(`../../posts/${params.postId}.mdx`))
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<Post />
</Suspense>
</div>
)
}
And my file structure is like this:
/posts
--post1.mdx
/src
--/views
-----BlogPost.tsx
-----BlogList.tsx
So, whenever I want to add a new blog post I basically add a new .mdx file to the repository. However I don't want to edit any component. I mean, I don't want to have a variable that keeps list of the list of blog posts like following.
export const listOfBlogPosts = ["post1", "post2", ...]
Because it will need to be updated every time I add a new post. Instead I would like to get list of files dynamically in BlogList component.
import { Link } from 'react-router-dom'
import style from './BlogList.module.css'
export const BlogList = () => {
const listOfBlogPosts = ['post1', 'post2'] // get somehow dynamically
return (
<div className={style}>
{listOfBlogPosts.map((post) => {
return <Link to={post}> {post}</Link>
})}
</div>
)
}
How can achieve this? Is there a logical problem with my approach(i.e keeping dynamic files in the repo)? Any suggestion or comment would be appreciated

How can I dynamically add images to my pages in Gatsby using the image component?

So I have the image component that renders 2 different images, but I want to be able to just add my image component to any page and just pass in the specific image as a prop instead of having to hard code a new query for each image I need to use.
If I had 50 images, I'd like to just pass image-50.jpg in as a prop instead of making a specific query for it. Is there a way to do that with graphql in gatsby?
Here is my current image component code
import { graphql, useStaticQuery } from "gatsby"
import Img from "gatsby-image"
import React from "react"
const Image = () => {
const data = useStaticQuery(graphql`
query {
astronaut: file(relativePath: { eq: "gatsby-astronaut.png" }) {
childImageSharp {
fluid(maxHeight: 600) {
...GatsbyImageSharpFluid
}
}
}
person: file(relativePath: { eq: "profile.jpg" }) {
childImageSharp {
fluid(maxHeight: 600) {
...GatsbyImageSharpFluid
}
}
}
}
`)
return (
<>
<Img fluid={data.astronaut.childImageSharp.fluid} />
<Img fluid={data.person.childImageSharp.fluid} />
</>
)
}
export default Image
Is there a way to just add the image dynamically and then render it to a new page?
Something like this <Image src={"profile.jpg"} />
I don't know how I could add that to the graphql and imagine I have 50 images, then I would have to either map through all 50 or manually add each query and that doesn't make sense
Believe it or not you cannot create a fully dynamic image component using a gastby-image without risking a (possibly very large) bloat in your bundle size. The problem is that static queries in Gatsby do not support string interpolation in it's template literal. You would need to search through all the files each time you use the component.
There are some solutions you can try in an existing SO post found here.
You can always use graphql fragments and write something like the below for your queries and then conditionally render the proper image based on a file name passed via props in your Image component but alas this also pretty clunky:
export const fluidImage = graphql`
fragment fluidImage on File {
childImageSharp {
fluid(maxWidth: 1000) {
...GatsbyImageSharpFluid
}
}
}
`;
export const data = graphql`
query {
imageOne: file(relativePath: { eq: "one.jpg" }) {
...fluidImage
}
imageTwo: file(relativePath: { eq: "two.jpg" }) {
...fluidImage
}
imageThree: file(relativePath: { eq: "three.jpg" }) {
...fluidImage
}
}
`
// accessed like this
<Img fluid={data.imageOne.childImageSharp.fluid} />
// or this
<Img fluid={data.imageTwo.childImageSharp.fluid} />
// or this, dynamically (if you had a prop called imageName)
<Img fluid={data.[`${props.imageName}`].childImageSharp.fluid} />
As Apena's answer explains, it's tricky to work like that with Gatsby's Image. However, I must say that you can bypass it in different ways depending on the filesystem used and how the data is structured.
Keep in mind that if you set properly the filesystem in your gatsby-config.js, you are allowing Gatsby to recognize and to find all your images in your project, making them queryable and allowing them to be used by Gatsby Image component.
const path = require(`path`)
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: path.join(__dirname, `src`, `images`),
},
},
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
],
}
You can find much better ways than querying each image in a staticQuery filtered by the path, it's not true that is the only way to achieve it. Of course, if you are using a staticQuery approach, the limitation of making it dynamic forces you to make each query separately.
First of all, you need to know the difference between staticQuery and page query to understand which fits you and the limitations of them.
If you use a page query, you can always create an approach like the following one:
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../components/layout'
class ProductPage extends React.Component {
render() {
const products = get(this, 'props.data.allDataJson.edges')
return (
<Layout>
{products.map(({ node }) => {
return (
<div key={node.name}>
<p>{node.name}</p>
<Img fluid={node.image.childImageSharp.fluid} />
</div>
)
})}
</Layout>
)
}
}
export default ProductPage
export const productsQuery = graphql`
query {
allDataJson {
edges {
node {
slug
name
image{
publicURL
childImageSharp{
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
}
`
In the example above, you are using a page query to retrieve all images from a JSON file. If you set the path in your filesystem, you will be able to retrieve them using the GraphQL fragments. This approach is the more dynamic you can afford when dealing with Gatsby Image and it's better to query one by one.
The idea remains the same for other filesystems, this is just an adaptable approach. If you are using a CMS like Contentful, you can download the assets and query them dynamically in the same way since the filesystem allows you to do it.
Pages queries are only allowed in page components (hence the name) so, if you want to use it in a React standalone component to make it reusable, you will need to pass via props (or reducer) to your desired component and render the Gatsby image based on the received props.

Storybook Mock GraphQL (Gatsby)

I am using Gatsby to create a blog. Gatsby can use markdown with GraphQL to "automagically" create post pages for you. I was wondering using the Gatsby example here.
In storybook UI what is the best way to "mock" out the graphql query and replace it with our markdown data. So that I can test this component in Storybook UI. For example if I have a blog template that looks something like:
import { graphql } from 'gatsby';
import React from 'react';
export default function Template({
data, // this prop will be injected by the GraphQL query below.
}) {
const { markdownRemark } = data; // data.markdownRemark holds your post data
const { frontmatter, html } = markdownRemark;
return (
<div className="blog-post-container">
<div className="blog-post">
<h1>{frontmatter.title}</h1>
<h2>{frontmatter.date}</h2>
<div
className="blog-post-content"
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>
</div>
);
}
export const pageQuery = graphql`
query($slug: String!) {
markdownRemark(frontmatter: { slug: { eq: $slug } }) {
html
frontmatter {
date(formatString: "MMMM DD, YYYY")
slug
title
}
}
}
`;
Thanks in advance
You can probably modify the webpack configuration of Storybook to use the NormalModuleReplacementPlugin to mock the whole gatsby package. Then export a graphql method from your mock that you can manipulate in your stories.
Alternatively, split your component into a pure component and a component that performs the query and just use the pure component as suggested in https://www.gatsbyjs.org/docs/unit-testing/

contentful api markdown conversion to HTML

Is there any simple way to convert markdown text from contentful api to render into html code to be display on html page. I have tried using pagedown and some similar techniques , but none seem to work for me .
I'm a customer success manager at Contentful -
You can check out a list of recommended parsers by language on the our FAQ.
Also, feel free to send us messages on Intercom via our UI by clicking the 'Talk to Us' link :)
I know I'm late but here's the solution using handlebars:
var marked = require('marked');
marked.setOptions({
renderer: new marked.Renderer(),
sanitize: true,
smartLists: true,
smartypants: true
});
//Home
router.get('/', (req, res) => {
client.getEntry('<ENTRY_ID>')
.then( (entry)=> {
entry.fields.body = marked(entry.fields.body);
res.render('static/index',
{
entry: entry,
user: req.user
});
}).catch( (err) => {
console.log(err);
})
});
Then in our index.hbs template we can call the markdown variable in this case (entry.fields.body) by using {{{}}} to prevent escaping.
{{{entry.fields.body}}}
Here's how I did it with React:
class NewsDetail extends React.Component {
render() {
const body = marked(this.props.body || "");
return (
<div className="news-detail">
<h2>{this.props.title}</h2>
<div dangerouslySetInnerHTML={ { __html: body } }></div>
</div>
);
}
}
The markdown content is stored in the body attribute of the NewsDetail tag (via a short function that maps contentful data structure to my app structure).
The HTML page has this script tag to pull in the marked function:
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.6/marked.min.js"></script>
I have used ReactMarkdown module: in case you also have a react app: https://github.com/rexxars/react-markdown
Example: npm install --save react-markdown
const React = require('react')
const ReactDOM = require('react-dom')
const ReactMarkdown = require('react-markdown')
const input = '# This is a header\n\nAnd this is a paragraph'
ReactDOM.render(
<ReactMarkdown source={input} />,
document.getElementById('container')
)
I am passing markdown through props and using this module inside of my child component.
I also did the same as margarita in a react app but in the child component and I pulled my markdown from contentful.
I installed react-markdown package
with
npm install react-markdown
import React from "react";
import ReactMarkdown from "react-markdown";
const AboutIntro = (props) => {
return (
<div>
<h2 className="about__intro-title">
{props.aboutTitle}
</h2>
<ReactMarkdown>
{props.aboutCopy}
</ReactMarkdown>
</div>
)
}
export default AboutIntro;
hope this helps

Resources