Next.js passing additional data to dynamic routes - reactjs

I would like to use the film title in my route URL (eg films/fletch), but the subsequent getServerSideProps request requires the episode_id.
How do I pass both film.episode_id and film.title to films/[id]/index.js?
Movies.js
<Link
href={{
pathname: `/films/[id]`,
query: {
id: film.episode_id
},
}}
as={`/films/${encodeURIComponent(film.title)}`} >
<a>{film.title}</a>
</Link>
films/[id]/index.js
import {useRouter} from 'next/router'
const movie = () => {
const router = useRouter();
console.log(router);
const { id } = router.query
return (
<div>Movie page for <strong>{id}</strong></div>
)
}

You can use context.query to get the query params of the link.
// films/[id]/index.js
import PropTypes from 'prop-types'
const Movie = ({ query }) => {
return (
<div>Movie page for <strong>{query.id}</strong></div>
)
}
Movie.propTypes = {
query: PropTypes.shape({
id: PropTypes.string,
})
}
export async function getServerSideProps(context) {
return {
props: { query: context.query }
}
}
export default Movie

Related

Next.js: How to get the URL paramters [duplicate]

When I click on a link in my /index.js, it brings me to /about.js page.
However, when I'm passing parameter name through URL (like /about?name=leangchhean) from /index.js to /about.js, I don't know how to get it in the /about.js page.
index.js
import Link from 'next/link';
export default () => (
<div>
Click{' '}
<Link href={{ pathname: 'about', query: { name: 'leangchhean' } }}>
<a>here</a>
</Link>{' '}
to read more
</div>
);
Use router-hook.
You can use the useRouter hook in any component in your application.
https://nextjs.org/docs/api-reference/next/router#userouter
pass Param
import Link from "next/link";
<Link href={{ pathname: '/search', query: { keyword: 'this way' } }}><a>path</a></Link>
Or
import Router from 'next/router'
Router.push({
pathname: '/search',
query: { keyword: 'this way' },
})
In Component
import { useRouter } from 'next/router'
export default () => {
const router = useRouter()
console.log(router.query);
...
}
Using Next.js 9 or above you can get query parameters:
With router:
import { useRouter } from 'next/router'
const Index = () => {
const router = useRouter()
const {id} = router.query
return(<div>{id}</div>)
}
With getInitialProps:
const Index = ({id}) => {
return(<div>{id}</div>)
}
Index.getInitialProps = async ({ query }) => {
const {id} = query
return {id}
}
url prop is deprecated as of Next.js version 6:
https://github.com/zeit/next.js/blob/master/errors/url-deprecated.md
To get the query parameters, use getInitialProps:
For stateless components
import Link from 'next/link'
const About = ({query}) => (
<div>Click <Link href={{ pathname: 'about', query: { name: 'leangchhean' }}}><a>here</a></Link> to read more</div>
)
About.getInitialProps = ({query}) => {
return {query}
}
export default About;
For regular components
class About extends React.Component {
static getInitialProps({query}) {
return {query}
}
render() {
console.log(this.props.query) // The query is available in the props object
return <div>Click <Link href={{ pathname: 'about', query: { name: 'leangchhean' }}}><a>here</a></Link> to read more</div>
}
}
The query object will be like: url.com?a=1&b=2&c=3 becomes: {a:1, b:2, c:3}
For those looking for a solution that works with static exports, try the solution listed here: https://github.com/zeit/next.js/issues/4804#issuecomment-460754433
In a nutshell, router.query works only with SSR applications, but router.asPath still works.
So can either configure the query pre-export in next.config.js with exportPathMap (not dynamic):
return {
'/': { page: '/' },
'/about': { page: '/about', query: { title: 'about-us' } }
}
}
Or use router.asPath and parse the query yourself with a library like query-string:
import { withRouter } from "next/router";
import queryString from "query-string";
export const withPageRouter = Component => {
return withRouter(({ router, ...props }) => {
router.query = queryString.parse(router.asPath.split(/\?/)[1]);
return <Component {...props} router={router} />;
});
};
Get it by using the below code in the about.js page:
// pages/about.js
import Link from 'next/link'
export default ({ url: { query: { name } } }) => (
<p>Welcome to About! { name }</p>
)
I know 2 ways to do this:
A Server-Side way, and a Client-Side way.
Method #1: SSR (Server-Side Rendering):
You should use Query Context for that page.
So use getServerSideProps instead of getStaticProps
import React from "react";
export async function getServerSideProps(context) {
const page = (parseInt(context.query.page) || 1).toString();
// Here we got the "page" query parameter from Context
// Default value is "1"
const res = await fetch(`https://....com/api/products/?page=${page}`);
const products = await res.json();
return {props: {products: products.results}}
// will be passed to the page component as props
}
const Page = (props) =>{
const products = props.products;
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>);
}
export default Page
The reason is that: this data cannot be pre-rendered ahead of user's request, so it must be Server-Side Rendered (SSR) on every request.
Static Pages: Use getStaticProps
Changing Content: use getServerSideProps
And here the content is changing based on query Parameters
Reference: https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props
Method #2: Next Router (Client Side):
import {useState, useEffect} from "react";
import { useRouter } from 'next/router'
const Page = () =>{
const [products, setProducts] = useState([]);
const [page, setPage] =useState((useRouter().query.page || 1).toString());
// getting the page query parameter
// Default value is equal to "1"
useEffect(()=>{
(async()=>{
const res = await fetch(`https://....com/api/products/?page=${page}`);
const products = await res.json();
setProducts(products.results);
// This code will be executed only once at begining of the loading of the page
// It will not be executed again unless you cahnge the page
})()
},[page]);
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
export default Page
Reference: https://nextjs.org/docs/api-reference/next/router
If you need to retrieve a URL query from outside a component:
import router from 'next/router'
console.log(router.query)
import { useRouter } from 'next/router';
function componentName() {
const router = useRouter();
console.log('router obj', router);
}
We can find the query object inside a router using which we can get all query string parameters.
Using {useRouter} from "next/router"; helps but sometimes you won't get the values instead u get the param name itself as value.
This issue happens when u are trying to access query params via de-structuring like:
let { categoryId = "", sellerId = "" } = router.query;
and the solution that worked for me is try to access the value directly from query object:
let categoryId = router.query['categoryId'] || '';
let sellerId = router.query['sellerId'] || '';
Post.getInitialProps = async function(context) {
const data = {}
try{
data.queryParam = queryString.parse(context.req.url.split('?')[1]);
}catch(err){
data.queryParam = queryString.parse(window.location.search);
}
return { data };
};
import { useRouter } from 'next/router'
const Home = () => {
const router = useRouter();
const {param} = router.query
return(<div>{param}</div>)
}
Also you can use getInitialProps, more details refer the below tutorial.
get params from url in nextjs
What worked for me in Nextjs 13 pages in the app directory (SSR)
Pass params and searchParams to the page:
export default function SomePage(params, searchParams) {
console.log(params);
console.log(searchParams);
return <div>Hello, Next.js!</div>;
With some builds there may be a bug that can be solved by adding:
export const dynamic='force-dynamic';
especially when deploying on Vercel.
ref: https://beta.nextjs.org/docs/api-reference/file-conventions/page#searchparams-optional
https://github.com/vercel/next.js/issues/43077

how to not fetch fragment data until component renders react GraphQL

I thought that relay modern implemented a system whereby it would not try to fetch data until it was rendering the component that declared it. I am talking about fragment components. I have tried to test this but it is fetching all the data.
import React from "react";
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import {
RelayEnvironmentProvider,
} from "react-relay/hooks";
import "./App.css";
import QueryLoaderComponent from "./QueryLoaderComponent";
import QueryComponent from "./QueryComponent";
async function fetchGraphQL(text: string, variables: Record<any, any>) {
// Fetch data from GitHub's GraphQL API:
const response = await fetch("https://countries.trevorblades.com/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: text,
variables,
}),
});
// Get the response as JSON
return await response.json();
}
async function fetchRelay(params: any, variables: any) {
console.log(
`fetching query ${params.name} with ${JSON.stringify(variables)}`
);
return fetchGraphQL(params.text, variables);
}
// Export a singleton instance of Relay Environment configured with our network function:
const environment = new Environment({
network: Network.create(fetchRelay),
store: new Store(new RecordSource()),
});
function App() {
return (
<RelayEnvironmentProvider environment={environment}>
{/* <QueryLoaderComponent /> */}
<QueryComponent />
</RelayEnvironmentProvider>
);
}
export default App;
import { useState } from "react";
// #ts-ignore
import graphql from "babel-plugin-relay/macro";
import { QueryComponentQuery } from "./__generated__/QueryComponentQuery.graphql";
import { PreloadedQuery, useLazyLoadQuery, usePreloadedQuery } from "react-relay";
// import FragmentComponent from "./FragmentComponent";
const query = graphql`
query QueryComponentQuery($id: ID!) {
country(code: $id) {
name
...FragmentComponent_country
}
}
`;
interface Props {
// queryRef: PreloadedQuery<QueryComponentQuery>;
}
const QueryComponent = ({
// queryRef
}: Props) => {
const data = useLazyLoadQuery<QueryComponentQuery>(query, { id: "US"});
const [showContinent, setShowContinent] = useState(false);
return (
<div>
<button onClick={() => setShowContinent(!showContinent)}>
{showContinent ? "Hide" : "Show"} continent
</button>
<h1>{data.country?.name}</h1>
{/* <ul>
{data.countries.map((country: any) => (
<li key={country.name}>
{country.name}{" "}
{showContinent && <FragmentComponent country={country} />}
</li>
))}
</ul> */}
</div>
);
};
export default QueryComponent;
import { useFragment } from "react-relay";
// #ts-ignore
import graphql from "babel-plugin-relay/macro";
import { FragmentComponent_country$key } from "./__generated__/FragmentComponent_country.graphql";
export const fragment = graphql`
fragment FragmentComponent_country on Country {
continent {
name
}
}
`;
interface Props {
country: FragmentComponent_country$key;
}
const FragmentComponent = ({ country }: Props) => {
const data = useFragment(fragment, country);
return <div>{data.continent.name}</div>;
};
export default FragmentComponent;
this is fetching the data for the fragment component even though it is not rendering the fragment component. is there a way to defer it until it is rendering the component?
use
React Suspense
on the fragment or anywhere where fetching happens as wrapper

Nextjs getInitialProps query.id is undefined

I am trying to get the id from the route as http//localhost:3000/portfolios/helloworld so id is helloworld. But i get an error that says TypeError: Cannot destructure property 'id' of 'query' as it is undefined.
const PortfolioDetail = ({ query }) => {
const { id } = query;
return <h1>I am Details Page with ID: {id}</h1>;
};
PortfolioDetail.getInitialProps = ({ query }) => {
return { query };
};
export default PortfolioDetail;
I tried the same thing with class component but the error was same.
// class PortfolioDetail extends React.Component {
// static getInitialProps({ query }) {
// return { query };
// }
// render() {
// const id = this.props.query.id;
// return <h1>I am Detail Page with id : {id} </h1>;
// }
// }
// export default PortfolioDetail;
this is my project structure you can see below image
It only works and i can get my id using useRouter i showed below.
import { useRouter } from 'next/router';
import React from 'react';
const PortfolioDetail = () => {
const router = useRouter();
const id = router.query.id
return <h1>I am Details Page with ID: {id}</h1>;
};
PortfolioDetail.getInitialProps = ({ query }) => {
return { query };
};
export default PortfolioDetail;
I am stuck at this point and i really wanna know why it won't work.
I've got it, you have an error in your _app:
import '../styles/index.scss';
import 'bootstrap/dist/css/bootstrap.min.css';
// Don't need to spread pageProps here
const MyApp = ({ Component, ...pageProps }) => {
return <Component {...pageProps} />;
};
export default MyApp;
It should be:
const MyApp = ({ Component, pageProps }) => {
return <Component {...pageProps} />;
};
Why dont use it like the code shown below
export default function FirstPost({ id }) {
console.log("-->", id);
return (
<>
{id}sdlfdfdlkj
</>
);
}
FirstPost.getInitialProps = ({ query }) => {
return { id: query?.id };
};

How to fetch category page data inside component in Next.js?

I would like to create a category page containing all tags added to articles. When clicking on a tag it should show a page with all articles containing that specific tag.
I'm using Next.js, SSG, and fetching the articles from Contentful with the following GraphQL query:
export async function getArticles() {
const articlesQuery = gql`
{
articleCollection {
items {
title
slug
excerpt
date
contentfulMetadata {
tags {
name
id
}
}
featuredImage {
title
url
width
height
}
}
}
}
`;
return graphQLClient.request(articlesQuery);
}
The contentfulMetadata is where the tags come from:
contentfulMetadata {
tags {
name
id
}
}
I've then created a CategorySection component:
import styled from "styled-components";
import { getArticles } from "../../utils/contentful";
import Link from "next/link";
export async function getStaticProps() {
const categories = await getArticles();
return {
props: {
categories: categories.articleCollection.items,
},
};
}
export default function CategorySection({ categories }) {
return (
<Wrapper>
<ContentWrapper>
<CategoryWrapper>
{categories.map((category) => {
return (
<Link href={`/articles/categories/${category.tags.name}`}>
<Categories key={category.tags.id}>
{category.tags.name}
</Categories>
</Link>
);
})}
</CategoryWrapper>
</ContentWrapper>
</Wrapper>
);
}
The CategorySection component gives me the following error message:
TypeError: Cannot read property 'map' of undefined"
Below is my /pages/articles/categories/[slug].jsx file:
import styled from "styled-components";
import { getArticles, getArticle } from "../../utils/contentful";
export async function getStaticPaths() {
const data = await getArticles();
return {
paths: data.articleCollection.items.map((article) => ({
params: { slug: article.contentfulMetadata.tags.id },
})),
fallback: false,
};
}
export async function getStaticProps(context) {
const data = await getArticle(context.params.slug);
return {
props: { article: data.articleCollection.items[0] },
};
}
export default function Category({ article }) {
return <h1>{article.contentfulMetadata.tags.name}</h1>;
}
I'm getting the error below:
Error: A required parameter (slug) was not provided as a string in
getStaticPaths for /articles/categories/[slug]
Can you help me understand how I create dynamic pages from my categories (tags)?
getStaticProps can only be used in page components, so in your case it'll be completely ignored in your CategorySection component. You'll need to fetch the data at the page level and pass it to the component where you want to use it.
One possible solution is to simply pass the data as a prop down to the desired component.
// pages/article
import { getArticles } from "../../utils/contentful";
export async function getStaticProps() {
const categories = await getArticles();
return {
props: {
categories: categories.articleCollection.items
}
};
}
export default function ArticlePage({ categories }) {
return (
<CategorySection categories={categories} />
);
}

getStaticProps returns an empty object

I'm using nextJS V9.5.5 with wp-graphql and apolloClient to get data from WordPress. Everything works fine, but when I try to return context (in the purpose of getting query) from getStaticProps() like it's described in docs, it returns an empty object.
Custom App:
import React from "react";
import getConfig from "next/config";
import LayoutOuter from "../components/LayoutOuter";
import "bootstrap/dist/css/bootstrap.css";
import { ApolloProvider } from "#apollo/client";
import { useApollo } from "../lib/apolloClient";
import { initializeApollo } from "../lib/apolloClient";
import { gql } from "#apollo/client";
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const { DOMAIN } = publicRuntimeConfig;
function CustomApp({ pageProps, Component, props }) {
const apolloClient = useApollo(pageProps.initialApolloState);
return (
<ApolloProvider client={apolloClient}>
{console.log("_app", props)}
<LayoutOuter>
<Component {...pageProps} />
</LayoutOuter>
</ApolloProvider>
);
}
CustomApp.getInitialProps = async (ctx) => {
const apolloClient = initializeApollo();
await apolloClient.query({
query: gql`
{
// my graphql query here
}
`,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
ctx: JSON.stringify(ctx),
},
};
};
export default CustomApp;
One of the page:
import React, { Component, useEffect, useState } from "react";
import getConfig from "next/config";
import { NextSeo } from "next-seo";
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const { DOMAIN, SITENAME } = publicRuntimeConfig;
import { initializeApollo } from "../lib/apolloClient";
import { gql } from "#apollo/client";
import "./services.module.scss";
const Home = (props) => {
let currentPage = Object.values(props.initialApolloState.ROOT_QUERY)[1];
const {
title,
metadesc,
metaRobotsNoindex,
metaRobotsNofollow,
metaRobotsAdv,
opengraphTitle,
opengraphDescription,
opengraphImage,
twitterTitle,
twitterDescription,
twitterImage,
} = currentPage.seo;
return (
<>
{console.log("project", props)}
<NextSeo
noindex={metaRobotsNoindex}
nofollow={metaRobotsNofollow}
title={title != "" ? title : `${props.data.pagetitle} - ${SITENAME}`}
description={metadesc}
canonical={DOMAIN}
openGraph={{
url: DOMAIN,
title:
opengraphTitle != ""
? opengraphTitle
: `${props.data.pagetitle} - Garrison Collection`,
description: opengraphDescription,
images: [
{
url: opengraphImage,
width: 800,
height: 600,
alt: { SITENAME },
},
],
site_name: { SITENAME },
}}
/>
<p>works</p>
</>
);
};
export async function getStaticProps(context) {
const apolloClient = initializeApollo();
await apolloClient.query({
query: gql`
{
project(id: "ca-souls", idType: SLUG) {
seo {
canonical
metaDesc
metaKeywords
metaRobotsNofollow
metaRobotsNoindex
opengraphAuthor
opengraphDescription
opengraphModifiedTime
opengraphPublishedTime
opengraphPublisher
opengraphSiteName
opengraphTitle
opengraphType
opengraphUrl
title
twitterDescription
twitterTitle
}
}
}
`,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
context: JSON.stringify(context) || null,
},
revalidate: 1,
};
}
export default Home;
Here is the log result:
How could I get context.query?
The context parameter includes previewData contains the preview data set by setPreviewData. This means including function, therefore, unable to serealize. Take values out from context.params.

Resources