How to get the current url of each page in Gatsby - javascript

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
}
}
}
`

Related

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

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
}
}

Router push not working from within useEffect hook

I have the following component where I am expecting to go to another path via router's push method immediately cos router push is inside the useEffect` hook.
But router push never seems to happen. The ShowLoading component is just a loading screen (shows a loading spinner).
The page is just stuck on the loading spinner screen.
What am I doing wrong, why am I not being pushed to the new page? Pls advice. Thanks.
import React from 'react';
import Cookies from 'js-cookie';
import { ShowLoading } from '../../ShowLoading';
import { withRouter } from 'react-router';
const MyComponent = ({
router: { push, location },
}) => {
React.useEffect(() => {
// this cookie gets set thus clearly we are entering this useEffect.
Cookies.set('temp', '1');
const value = 'test';
const params = location.search ? `${location.search}` : '';
// seems like this has no effect, no pushing and moving to new page path.
push(`/${value}/my_path${params}`);
}, []);
return (<ShowLoading />);
};
export default (withRouter(MyComponent);
P.S: Goes to a new path as expected if I do the following but looking to do it via a router.
window.location.pathname = `/${value}/my_path${params}`;
You can get match, history and location as props when using withRouter. So you can get the push from history like this:
import React from 'react';
import Cookies from 'js-cookie';
import { ShowLoading } from '../../ShowLoading';
import { withRouter } from 'react-router';
const MyComponent = ({
history,
location
}) => {
React.useEffect(() => {
// this cookie gets set thus clearly we are entering this useEffect.
Cookies.set('temp', '1');
const value = 'test';
const params = location.search ? `${location.search}` : '';
history.push(`/${value}/my_path${params}`);
}, []);
return (<ShowLoading />);
};
export default withRouter(MyComponent);

Why does router.query return an empty object in NextJS on first render?

My url is: http://localhost:3000/company/60050bd166cb770942b1dadd
I want to get the value of the id by using router.query. However when I console log router.query, it returns an empty object first and then return the object with data. This results in bugs in other parts of my code as I need the value of the id to fetch other data.
This is my code:
import { useRouter } from 'next/router';
import styles from './CompanyId.module.css';
import { useQuery } from '#apollo/client';
import { COMPANY_DETAILS } from '../../queries/company';
const CompanyDetails = () => {
const router = useRouter();
console.log(router.query);
const { loading, data } = useQuery(COMPANY_DETAILS, {
variables: { _id: companyId },
});
return (
<div className={styles.container}>
{loading ? <h1>Loading</h1> : <h1>{data.company.name}</h1>}
</div>
);
};
export default CompanyDetails;
My program is crashing right now because the companyId variable is empty on the first render. Is there anyway to go around this problem?
In Next.js:
Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e query will be an empty object ({}).
After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.
I solved it by using useLazyQuery instead of useQuery, and wrapped the function inside useEffect.
The problem was that NextJS's router.query returns an empty object on the first render and the actual object containing the query comes in at the second render.
This code works:
import React, { useEffect } from 'react';
import { useRouter } from 'next/router';
import styles from './CompanyId.module.css';
import { useLazyQuery } from '#apollo/client';
import { COMPANY_DETAILS } from '../../queries/company';
const CompanyDetails = () => {
const router = useRouter();
const [getCompany, { loading, data }] = useLazyQuery(COMPANY_DETAILS);
useEffect(() => {
if (router.query.companyId) {
getCompany({ variables: { _id: router.query.companyId } });
}
}, [router.query]);
if (loading) return <h1>Loading....</h1>;
return (
<div className={styles.container}>
{data && <h1>{data.company.name}</h1>}
</div>
);
};
export default CompanyDetails;

set cookie before running fetch in getInitialProps

I have a Next.js app, I'm using getInitialProps in my _app.js in order to be able to have persistent header and footer. However, I'm also needing to set data in a Context, and I need to be able to fetch the data based off of a cookie value. I've got the basics working just fine, however my _app sets the cookie on the first load, and then when I refresh the page it pulls in the appropriate data. I'm wondering if there's a way to be able to set the cookie first before fetching the data, ensuring that, if there's a cookie present, it will always pull in that data on the first load? Here is my _app.js, and, while I'm still working on the dynamic cookie value in my cookies.set method, I'm able to fetch the right data from my Prismic repo by hard-coding sacramento-ca for now, as you'll see. All I'm really needing is the logic to ensure that the cookie sets, and then the data fetches.
_app.js
import React from 'react';
import { AppLayout } from 'components/app-layout/AppLayout';
import { Footer } from 'components/footer/Footer';
import { Header } from 'components/header/Header';
import { LocationContext } from 'contexts/Contexts';
import Cookies from 'cookies';
import { Client } from 'lib/prismic';
import NextApp, { AppProps } from 'next/app';
import 'styles/base.scss';
import { AppProvider } from 'providers/app-provider/AppProvider';
interface WithNavProps extends AppProps {
navigation: any;
location: string;
dealer?: any;
cookie: string;
}
const App = ({ Component, pageProps, navigation, dealer }: WithNavProps) => {
const { Provider: LocationProvider } = LocationContext;
const locationData = dealer ? dealer : null;
return (
<LocationProvider value={{ locationData }}>
<AppProvider>
<AppLayout>
<Header navigation={navigation} location={dealer} />
<Component {...pageProps} />
<Footer navigation={navigation} />
</AppLayout>
</AppProvider>
</LocationProvider>
);
};
export default App;
App.getInitialProps = async (appContext: any) => {
const appProps = await NextApp.getInitialProps(appContext);
const cookies = new Cookies(appContext.ctx.req, appContext.ctx.res);
try {
cookies.set('dealerLocation', 'sacramento-ca', {
httpOnly: true,
});
const { data: navigation } = await Client.getByUID('navigation', 'main-navigation', {
lang: 'en-us',
});
const results = await Client.getByUID('dealer', cookies.get('dealerLocation'), {
lang: 'en-us',
});
return {
...appProps,
navigation,
dealer: results,
};
} catch {
const { data: navigation } = await Client.getByUID('navigation', 'main-navigation', {
lang: 'en-us',
});
return {
...appProps,
navigation,
};
}
};

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