Get all posts components props in NextJS - reactjs

I'm moving my blog from Jekyll to NextJS and missing a feature to return all posts in one list.
From the official examples and other sources, I see that posts are written in markdown, and developers need to read a directory with posts, parse files, extract front matter, etc.
But I heavily use schema.org attributes, and all posts are written in pure HTML, I only replaced img elements with NextJS implementation.
So instead of using frontmatter, I renamed my posts' file extension to .js and rewrote its contents to this:
import Breadcrumbs from '/components/breadcrumbs';
import PostHeader from '/components/postheader';
export async function getStaticProps() {
return {
props: {
title: "How to start investing",
description: "How to start investing in 2022",
published: "2021-08-01",
modified: "2022-04-09",
tags: ["investing", "how-to"]
}
}
}
export default function Post() {
return <>
<Breadcrumbs />
<article itemscope itemtype="https://schema.org/TechArticle">
<PostHeader />
<div>
blah.. blah.. blah..
</div>
</article>
</>
}
So is there a way to get Component props by given folder path or something like that without parsing files?

import PropTypes from 'prop-types';
export default function Post(props) {
return <>
<Breadcrumbs />
<article itemscope itemtype="https://schema.org/TechArticle">
<PostHeader />
<div>
<p>{props.title}</p>
<p>{props.description}</p>
<p>{props.published}</p>
<p>{props.modified}</p>
<p>{props.tags}</p>
</div>
</article>
</>
}
ComponentName.prototype ={
title: String,
description:string,
published: VarDate,
modified: VarDate,
tags:String
}

I ended up storing all posts in a simple javascript list
export default ([
{
title: "How to start investing",
description: "How to start investing in 2022",
published: "2021-08-01",
modified: "2022-10-23",
tags: ["investing", "how-to"],
slug: "how-to-start-investing",
img: "startinvesting",
excerpt: `
Post excertp text...
`
},
{...}
])
and then importing it in GetStaticProps, filtering by post slug
import Image from 'next/image'
import posts from '../../../data/posts'
import Breadcrumbs from '../../../components/breadcrumbs'
import PostHeader from '../../../components/post-header'
export async function getStaticProps() {
return {
props: {
...posts.find(post => post.slug == 'how-to-start-investing')
}
}
}
export default function Post(props) {
return <>
<h1>{props.title}</h1>
Post text...
</>
}

Related

getStaticProps returning empty props in Next.js [duplicate]

I am making a simple website and I would like to fetch data from an API and display it on my component.
The problem is that the getStaticProps() method is never called.
Here is the code of the component :
import React from "react";
import {GetStaticProps, InferGetStaticPropsType} from "next";
type RawProject = {
owner: string;
repo: string;
link: string;
description: string;
language: string;
stars: number;
forks: number;
}
function Projects({projects}: InferGetStaticPropsType<typeof getStaticProps>) {
console.log(projects);
return (
<section id="projects" className="bg-white p-6 lg:p-20">
<h1 className="sm:text-4xl text-2xl font-medium title-font mb-4 text-gray-900 pb-6 text-center">
Quelques de mes projets
</h1>
{/*
<div className="container px-5 mx-auto">
<div className="flex flex-wrap">
{rawProjects.map((project: RawProject) => (
<ProjectCard
title={project.repo}
language={project.language}
description={project.description}
imageUrl="https://dummyimage.com/720x400"
repoUrl={project.link}
/>
))}
</div>
</div>
*/}
</section>
);
}
export const getStaticProps: GetStaticProps = async () => {
console.log("getStaticProps()");
const res = await fetch("https://gh-pinned-repos-5l2i19um3.vercel.app/?username=ythepaut");
const projects: RawProject[] = await res.json();
return !projects ? {notFound: true} : {
props: {projects: projects},
revalidate: 3600
};
}
export default Projects;
The full code can be found here : https://github.com/ythepaut/webpage/tree/project-section
I am not sure if the problem is caused by the fact that I use typescript, or that I use a custom _app.tsx
I tried the solutions from :
https://github.com/vercel/next.js/issues/11328
How to make Next.js getStaticProps work with typescript
but I couldn't make it work.
Could someone help me please ?
Thanks in advance.
getStaticProps() is only allowed in pages.
Your code at the moment is :
import Hero from "../sections/Hero";
import Contact from "../sections/Contact";
import Projects from "../sections/Projects"; // you cannot call getStaticProps() in this componenet
function HomePage(): JSX.Element {
return (
<div className="bg-gray-50">
<Hero />
<Projects />
<Contact />
</div>
);
}
export default HomePage;
Instead call getStaticProps() inside index.tsx and pass the props to the component something like this ::
import Hero from "../sections/Hero";
import Contact from "../sections/Contact";
import Projects from "../sections/Projects";
function HomePage({data}): JSX.Element {
return (
<div className="bg-gray-50">
<Hero />
<Projects data={data} />
<Contact />
</div>
);
}
export const getStaticProps: GetStaticProps = async () => {
console.log("getStaticProps()");
const res = await fetch("https://gh-pinned-repos-5l2i19um3.vercel.app/?username=ythepaut");
const projects: RawProject[] = await res.json();
return !projects ? {notFound: true} : {
props: {projects: projects},
revalidate: 3600
};
}
export default HomePage;
Data fetching methods in NextJs like getStaticProps runs only on the server. Hence it works only in pages, not in a regular react component
Please Check their docs
For data fetching in Normal Components, You can only do client-side Rendering. NextJS recommends using this library SWR
According to their docs
SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally, come with the up-to-date data.
You can only use getInitialProps, getServerSideProps, getStaticProps in Next.js pages
I checked your project and saw that your Project.tsx was in a component folder, but it needs to be in pages folder for those functions to work.
I got a similar bad experience because of bad service worker implementation, if you found it works with "Empty Cache and Hard Reload", you should check your service-worker code, you may don't want to cache any pages.
In pages folder you must export getStaticProps too
Example
export { default } from '../../games/tebak-kata/game';
export { getStaticPaths } from '../../games/tebak-kata/game';
export { getStaticProps } from '../../games/tebak-kata/game';

'Cannot Find Module' when using an Image in React

I am using React and I am running into an issue importing images in a component. Now I know how to use the import method, which is fine for some things but in this case the image src is part of an array of objects I need to map over.
So let me start with if I do this directly: <img src={require('../../images/leadinvestigator.jpg')} /> It works just fine.
However if I do this: <img src={require(member.image)} /> or <img src={require(${member.image})} /> I get:
react-dom.development.js:11340 Uncaught Error: Cannot find module '../../images/leadinvestigator.jpg'
and if I do this: <img src={require({member.image})} /> I get an invalid token error
here is my component code:
import React from "react"
import "react-responsive-carousel/lib/styles/carousel.min.css"
import { Carousel } from 'react-responsive-carousel'
import './styles.css'
const Crew = () => {
const crewMembers = [
{
image: '../../images/leadinvestigator.jpg',
name: 'Bryant Richards',
title: 'Lead Investigator',
quote: 'In the end, it only matters what you believe'
}
]
return (
<Carousel
showArrows={true}
infiniteLoop={true}
showThumbs={false}
showStatus={false}
autoPlay={true}
interval={6100}
>
{
crewMembers ? crewMembers.map(member => (
<div>
<img src={require('../../images/leadinvestigator.jpg')} />
<div className="myCarousel">
<h3>{member.name}</h3>
<h4>Designer</h4>
<p>
It's freeing to be able to catch up on customized news and not be
distracted by a social media element on the same site
</p>
</div>
</div>
)) : null
}
</Carousel>
)
}
export default Crew
Try to import your image before put it in the object.
Add on top of your component:
import bryantImage from '../../images/leadinvestigator.jpg'
and then add this image to your object like that:
const crewMembers = [
{
image: bryantImage,
name: 'Bryant Richards',
title: 'Lead Investigator',
quote: 'In the end, it only matters what you believe'
}
]

Best way to pass images in static react app

I'm using react to create a web-app that has to be static so no API calls and I need to store some data client-side (I'm currently just using a single JSON file as it's really not large) but I'm struggling to pass images down, I'm storing the references as strings in the JSON file and passing them to the props but I'm getting all sorts of errors. Is there a better or more practical way to do this?
Code (I've added a variable to imitate the JSON structure):
const HomePage = () => {
let projectInfo: ProjectInfo = {
title: "Project",
description:
"A progressive web app.",
imgURL: "./TODO", //<- HERE
mainColour: "#323232",
secondaryColour: "#464646",
};
return (
<div id="carousel-container">
<Carousel labels={["1"]}>
<ProjectPanel info={projectInfo} />
</Carousel>
</div>
);
};
export default HomePage;
interface IProjectPanelProps {
info: ProjectInfo;
}
const ProjectPanel = (props: IProjectPanelProps) => {
return (
<div className="project-panel">
<h2>{props.info.title}</h2>
<p>{props.info.description}</p>
<img src={/* ?? */} alt=""></img>
</div>
);
};
You can use base64 images to store them.
See this post https://stackoverflow.com/a/42399865/1356340 for examples of how to use binary images in React.
You can import your image in your home page as a variable and pass that variable to your child component.
import myImage from 'your_image_path';
const HomePage = () => {
let projectInfo: ProjectInfo = {
title: "Project",
description:
"A progressive web app.",
imgURL: myImage
mainColour: "#323232",
secondaryColour: "#464646",
};
return (
<div id="carousel-container">
<Carousel labels={["1"]}>
<ProjectPanel info={projectInfo} />
</Carousel>
</div>
);
};
export default HomePage;
interface IProjectPanelProps {
info: ProjectInfo;
}
const ProjectPanel = (props: IProjectPanelProps) => {
return (
<div className="project-panel">
<h2>{props.info.title}</h2>
<p>{props.info.description}</p>
<img src={info.imgURL} alt=""></img>
</div>
);
};

Caching in Gatsby JS

I have this Gatsby app where when I visit home page everything loads at first including the Testimonials section. However, when I visit another page, for instance the Blog list page and then click on the link back to homepage the data on Testimonials component won't load and you will see a blank area of the app where the Testimonials section is placed. In order to see the Testimonials list, you will have the refresh the page again.
For the record the data on my Testimonials component are being pulled out on Strapi JS that is deployed on Heroku.
So far I got this on my index.js:
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/Layout"
import Header from "../components/Header"
import Testimonials from '../components/Testimonials'
import Blogs from '../components/Blogs'
import SEO from '../components/SEO'
export default function Home({ data }) {
const {
allStrapiBlogs: { nodes:blogs },
} = data
return (
<>
<SEO />
<div className="main-container">
<Layout>
<Header />
<Testimonials title="Testimonials" />
<Blogs title="Latest Blogs" blogs={blogs} showAllBlogLinks/>
<Map />
</Layout>
</div>
</>
)
}
export const query = graphql`
{
allStrapiBlogs(filter: {featured: {eq: true}}, sort: {fields: published_date, order: DESC}, limit: 6) {
nodes {
id
title
content
slug
published_date(formatString: "MMMM DD, YYYY")
featured_image {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`
And then on my Testimonials.js component:
import React from "react"
import { graphql, useStaticQuery } from "gatsby"
import Image from "gatsby-image"
import { FaStar } from "react-icons/fa"
import Title from './Title'
const query = graphql`
{
allStrapiTestimonials {
nodes {
id
name
cite
text
photo {
childImageSharp {
fluid{
...GatsbyImageSharpFluid_withWebp
}
}
}
}
}
}
`
const Testimonials = ({ title }) => {
const data = useStaticQuery(query)
const { allStrapiTestimonials: { nodes:testimonials } } = data
return (
<div className="testimonial-section section-padding" id="testimonial" data-aos="zoom-in">
<div className="container">
<div className="row">
<div className="col-lg-12">
<div className="section-title-two center">
<Title title={title} />
</div>
</div>
</div>
<div className="testimonial-wrap row">
<div className="testimonial-slider owl-carousel">
{ testimonials.map(item => {
const { id, name, cite, text, photo } = item
return(
<div className="col-xl-8 col-lg-10 col-12 ml-auto mr-auto" key={id}>
<div className="testimonial-item mt-40">
<div className="testimonial_img">
<Image fluid={photo.childImageSharp.fluid} alt={name} style={{ position: "absolute", overflow: "visible", display: "block", width: "211px", height: "207px" }} />
</div>
<div className="testimonial_content xs-mt-40">
<div className="testimonial_content_item mb-30">
<div className="testimonial_content__pro">
<h4 className="mb-10">{ name }</h4>
<p>{ cite }</p>
</div>
<ul className="d-none d-sm-inline-block">
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
</ul>
</div>
<div>
<p>{ text } </p>
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
</div>
)
}
export default Testimonials
Any idea what's causing this error? How can I fix it?
I've faced a similar issue a few months ago and the fix depends strictly on the implementation and your code. Basically, what is happening is that React's doesn't understand that he needs to rehydrate some components because pointing to some global objects as window or document of the DOM, outside the React's ecosystem (without using states) may block that rehydration, in your cause, because of jQuery.
All the possible solutions that bypass this issue will be patches (like trying to add the cache). The ideal solution would avoid using jQuery, which points directly to the DOM, with React, which manipulates the virtual DOM (vDOM).
There's nothing wrong with the code you've shared, however, based on some other questions that you did, you are using jQuery and using the window object, which prevents the rehydration of some React components. You should get rid of jQuery or using some React-based approach. Something like this should do the trick to force a loader across the whole site:
const Layout = ({ children }) => {
const [loader, setLoader]=useState(true);
useEffect(()=>{
setTimeout(()=> {
setLoader(false)
}, 400)
}, [])
return <section className={`site__wrapper`}>
<Header />
<main>{loader ? <Preloader/> : children}</main>
</section>;
};

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