Best way to use slug for querying Prisma without react hook? - javascript

Get post by ID ( slug ) from prisma when getStaticProps() before page build
So the issue is that I cannot use React hook in getStaticProps. I was going to get slug names with useRouter, then query for post by using the slug (postID), but I learned that I cannot run prisma inside of body components. Then I learned that I can use getStaticProps and getStaticPaths to query the post by its ID before build time.
How do I get N levels of slug names in getStaticProps?.
Code
/post/[...slugs].tsx
My url looks like: localhost:3000/post/postID/PostTitle
such as localhost:3000/post/b513-ad29e3cc67d9/Post%20Title
import { Post, PrismaClient } from '#prisma/client';
import { GetStaticPaths, GetStaticProps } from 'next';
import { useRouter } from 'next/router';
type postByIdProps = {
postById: Post
}
export default function PostDetail({postById}: postByIdProps) {
return (
<>
<div>
{postById.title}
</div>
</>
);
}
export const getStaticProps = async(context: any)=>{
// I can't ues React Hook here, but I don't know how to get slug name without the hook.
const router = useRouter();
const slugs: any = router.query.slugs;
const postId = slugs?.[0].toString()
//Prisma
const prisma = new PrismaClient()
const postById = prisma.post.findUnique({
where: {
postId: postId,
},
})
return postById
}
export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
return {
paths: [], //indicates that no page needs be created at build time
fallback: 'blocking' //indicates the type of fallback
}
}

This worked fro me, but if someone can improve this code, more than welcome.
How to Build a Fullstack App with Next.js, Prisma, and PostgreSQL
code
import { Post } from '#prisma/client';
import { GetStaticPaths, GetStaticProps } from 'next';
import prisma from '../api/prisma';
type postByIdProps = {
post: Post
}
export default function PostDetail({post}: postByIdProps) {
console.log("Post here,", post)
return (
<>
<div>
{post.title}
</div>
</>
);
}
export const getStaticProps = async({params}: any)=>{
const postId = params.slugs[0] //gets post's ID
const post = await prisma.post.findUnique({
where:{
postId: String(postId)
},
})
return {
props:{
post
}
}
}
export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
return {
paths: [], //indicates that no page needs be created at build time
fallback: 'blocking' //indicates the type of fallback
}
}

Related

NextJs how to solve Objects are not valid as a React child (found: object with keys {children}) issue

I'm building a nextjs-application and I crossed an issue with getStaticPaths. Inside the pages-folder, I have a file called [slug].tsx which contains this code:
import { Image } from "react-datocms";
import { request } from "../lib/datocms";
import { GetStaticProps, GetStaticPaths } from "next";
export default function Page({ pageData }) {
return (
<div>
<h1>{pageData.title}</h1>
</div>
);
}
const PATHS_QUERY = `
query MyQuery {
allPages {
slug
}
}
`;
export const getStaticPaths: GetStaticPaths = async (context) => {
const slugQuery = await request({
query: PATHS_QUERY,
preview: context.preview,
});
let paths = [];
slugQuery.allPages.map((path) => paths.push(`/${path.slug}`));
return {
paths,
fallback: false,
};
};
const PAGE_QUERY = `
query MyQuery($slug: String) {
page(filter: {slug: {eq: $slug}}) {
title
slug
id
}
}
`;
export const getStaticProps: GetStaticProps = async ({ params }) => {
const page = {
query: PAGE_QUERY,
variables: { slug: params.slug },
};
return {
props: {
pageData: page,
}
};
};
This gives me the error: Objects are not valid as a React child (found: object with keys {children}). If you meant to render a collection of children, use an array instead.
I have no clue what this means, so can anyone help me out?
****** UPDATE ******
I suspect my Navbar could have something to do with this. In my components folfer, I have a nav folder with a Navbar.tsx-file which looks like this:
const Navbar = ({ topNav }) => {
const menu_items = topNav.menuItems[0];
return (
<nav>
{menu_items.topNavigationItems.map((navitem, idx) => (
<div key={navitem.text}>
<NavItem {...navitem} />
</div>
))}
</nav>
)
}
export default Navbar;
the NavItem looks like this:
const NavItem = ({ text, path, active }) => {
return (
<Link href={path.slug}>
<a>
{text}
</a>
</Link>
);
};
export default NavItem;
The way you are building your paths array inside getStaticPaths is not quite right according to the new standards. You have to "push" an object with a key of params, which then contains an object with your slug.
Rewriting your getStaticPaths function would result in the following.
export const getStaticPaths: GetStaticPaths = async (context) => {
const slugQuery = await request({
query: PATHS_QUERY,
preview: context.preview,
});
const paths = slugQuery.allPages.map(path => {params: {slug: path.slug} });
return {
paths,
fallback: false,
};
};
You can read more about the getStaticPaths function in the official documentation.
EDIT: To be more specific on the error you're getting, you are trying to render an object as a JSX element, thus generating an error. Try and find the source of that error and fix it this way.

How to get the current url of each page in Gatsby

I am trying to implement copy to clipboard function to set the current url of each page ,
I have followed the Gatsby docs : gatsby
import React from "react"
import { graphql } from "gatsby"
const Page = ({ location, data }) => {
const canonicalUrl = data.site.siteMetadata.siteURL + location.pathname
return <div>The URL of this page is {canonicalUrl}</div>
}
export default Page
export const query = graphql`
query PageQuery {
site {
siteMetadata {
siteURL
}
}
}
`
eventually I get an error of Cannot read properties of undefined (reading 'pathname')
any idea how to solve this ?
Depending on the lifecycle of the requested page (Page) location may not be defined, hence pathname breaks.
Try adding a default value or optional chaining like:
import React from "react"
import { graphql } from "gatsby"
const Page = ({ location={}, data }) => {
const canonicalUrl = data.site.siteMetadata.siteURL + location?.pathname
return <div>The URL of this page is {canonicalUrl}</div>
}
export default Page
export const query = graphql`
query PageQuery {
site {
siteMetadata {
siteURL
}
}
}
`

Pass data from dynamic page to persistent <Layout>{children}</Layout> component in Next.js

I want to use a persistent layout in Next.js and pass some data to it from my dynamic Blog post page.
So for example having this code (from next.js documentation):
// pages/posts/[id].js
function Post({ post }) {
return <p>{post.title}</p>;
}
// Trying to export the post title here
export const postTitle = ({ post }) => {
return post.title;
};
export async function getStaticPaths() {
// Call an external API endpoint to get posts
const res = await fetch("https://.../posts");
const posts = await res.json();
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}));
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false };
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`);
const post = await res.json();
// Pass post data to the page via props
return { props: { post } };
}
export default Post;
How can I grab that {post.title} and use it in my persistent layout component like so:
// SiteLayout.js
import React from "react";
import { postTitle } from "../../pages/posts/[id]";
// Main Page Wrapper
const SiteLayout = ({ children }) => {
return (
<>
{postTitle && <p>{postTitle}</p>}
<main className="layout">{children}</main>
</>
);
};
export default SiteLayout;
I've spent hours trying to make this work so I would really appreciate any pointers! Thank you!

Can I use 'useSWR' with the contentful-client to create pagination?

I'm trying to create pagination with nextjs and the useSWR hook.
This is how I've currently done it, and it appears to be working... however I read in the docs that the key passed as the first parameter should be a unique string (usually a URL). I'm just passing the index to fetch the correct data. Will my approach mess up the caching? I'm not sure if I'm doing this correctly?
index.js
import React, { useState } from 'react'
import Page from '../components/page'
export default function IndexPage( ) {
const [pageIndex, setPageIndex] = useState(0)
return (
<div>
<Page index={pageIndex} />
<button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
<button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
</div>
)
}
And in my page.js
import useSWR from 'swr'
import { fetcher } from '../client/fetcher'
function Page({ index }) {
const { data } = useSWR(index, fetcher)
console.table(data)
return <div>nothing here, just testing</div>
}
export default Page
And finally the fetcher.js
import client from './contentful-client'
export async function fetcher(pageIndex = 1, limit = 3) {
const data = await client.getEntries({
content_type: 'posts',
skip: pageIndex * limit,
order: '-fields.publishDate',
limit,
})
if (data) {
return data
}
console.log('Something went wrong fetching data')
}
You may want to move the Contentful data fetching logic to the server as to not expose credentials and logic to the browser. This could be done using Next.js API routes.
// pages/api/posts.js
import client from '<path-to>/contentful-client' // Replace with appropriate path to file
export default async function handler(req, res) {
const { pageIndex = 1, limit = 3 } = req.query
const data = await client.getEntries({
content_type: 'posts',
skip: pageIndex * limit,
order: '-fields.publishDate',
limit,
})
res.json(data)
}
You could then refactor the code in your page to make a request against the newly created API route, passing the route URL as the key to useSWR.
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then(res => res.json())
function Page({ index }) {
const { data } = useSWR(`/api/posts?pageIndex=${index}`, fetcher)
console.table(data)
return <div>nothing here, just testing</div>
}
export default Page

Gatsby page generation not filtering out slug

I'm trying to generate Gatsby pages based on data in my CMS (Sanity).
I have created three pages in my CMS under the umbrella term "Second page", I use createPage in gatsby-node to generetate pages with the correct slugs.
All the pages are generated according to their slug from the CMS, but in my template component I cannot filter out data. I get the result for all three pages when I only need the result for the one page that matches the slug. My console.log in secondPage.js shows three arrays corresponding to the three items in my CMS
gatsby-node.js
// Create pages for docs
exports.createPages = ({ actions, graphql }) => {
const path = require(`path`);
const { createPage } = actions;
//const docTemplate = path.resolve("src/templates/docTemplate.js");
const secondPageTemplate = path.resolve("src/templates/secondPage.js");
return graphql(`
{
allSanitySecondPage {
edges {
node {
slug
}
}
}
}
`).then((result) => {
if (result.errors) {
Promise.reject(result.errors);
}
result.data.allSanitySecondPage.edges.forEach(({ node }) => {
createPage({
path: node.slug,
component: secondPageTemplate,
context: {
slug: node.slug,
},
});
});
});
};
secondPage.js (template)
import React from "react";
import { graphql } from "gatsby";
import Layout from "../components/layout";
const BlockContent = require("#sanity/block-content-to-react");
const secondPage = ({ data }) => {
// const pageData = data.sanitySecondPage.edges.node;
return (
<Layout>
<h1>Hello from the second page!</h1>
{console.log(data.sanitySecondPage)}
{/* <BlockContent blocks={pageData._rawBlockContent} /> */}
</Layout>
);
};
export const query = graphql`
query($slug: String!) {
sanitySecondPage(slug: { eq: $slug }) {
_rawBlockContent
}
}
`;
export default secondPage;
It turns out that all I needed was to write a stack overflow post to solve my own issue. Everything was correct, I miss understood the return of block content, it was supposed to be three array elements.

Categories