context parameter is empty on getServerSideProps when page refresh - reactjs

In one of my projects, I need to fill the meta keyword and the meta description by the data which is fetched from a REST API request.
so I used the getServerSideProps function, to fetch the response and pass it to the page.
Here's my getServerSideProps function
export async function getServerSideProps(context) {
function makeParam() {
let params = new URLSearchParams(context.params);
let keysForDel = [];
params.forEach((v, k) => {
if (v === 'undefined')
keysForDel.push(k)
});
keysForDel.forEach(k => {
params.delete(k)
});
return params.toString()
}
let response = await axios.post(
process.env.baseAddress + "getData.php",
qs.stringify({
data: "api/search/advance?" + makeParam()
}),
{
headers: {
'content-type': 'application/x-www-form-urlencoded'
}
})
return {
props: {
dataFromServer: response.data,
params: makeParam()
},
}
}
everything works fine in development mode (localhost), but after deploying, by refreshing the page Context parameter is an empty object.
this function was written in one of the pages that has one parameter called the city, which is shown below
I have already checked getServerSideProps props empty object.
as Ankri said
Can you please check, that you pass the pageProps to your custom Component?
here is my Component tag, which contains pageProps.
<Layout>
<Component {...pageProps} ref={current}/>
</Layout>

First make the file structure like this:
pages:
city:
[...slug].js
Note: city is folder!
Now you can get the first param like this:
export async function getServerSideProps(context) {
const slug = context?.query?.slug[0] || "";
const req = await axios.post(`${url}?advance=${slug}`)
// ... rest of the code
}
now if url looks like this => http://localhost:300/city/japan
slug = japan

Related

How to generate the list of route segment parameters for every pokemon using generateStaticParams in Next.js 13?

I am trying to use Nextjs 13's generateStaticParams to render static data into my web page. How do I generate the list of routes like /pokemon/[pokemonName] for every pokemon name using the generateStaticParams without providing hard-coded pokemon name (for example here I put "charmander" as argument for fetchData and then it generate route just for charmander)?
export const generateStaticParams = async (): Promise<PageParams[]> => {
const res = await fetchData("charmander");
return [
{
pokemonName: res?.data.pokemon.name,
},
];
};
Fetching pokemon moves from graphQL-pokeAPI:
const fetchData = async (pokemonName: string) => {
const POKEMON_MOVES = `
query pokemon($name: String!) {
pokemon(name: $name) {
id
name
sprites {
front_default
}
moves {
move {
name
}
}
}
}
`;
const res = await fetch("https://graphql-pokeapi.graphcdn.app/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: POKEMON_MOVES,
variables: { name: pokemonName },
}),
});
const moves = await res.json();
return moves;
};
export default function SpecificPokemon({ params }) {
const { pokemonName } = params;
const pokemonData = use(fetchData(pokemonName));
return (<h1>{pokemonName}</h1>...)
};
In the Nextjs13 beta docs, it said that generateStaticParams doesn't require any context parameters. So I can't pass pokemonName into generateStaticParams like this, right?
export const generateStaticParams = async (pokemonName: string) => {
const res = await fetchData(pokemonName);
I tried to just write fetchData("") and the page just blank. Also, it would be too many to write like this:
return [
{ pokemonName: "bulbasaur" },
{ pokemonName: "ivysaur" },
{ pokemonName: "venusaur" },
];
Also, is this due to my lack of JS / Next13 concept understanding?
They actually have a feature called getStaticPaths which was specifically designed for this. You can easily make a call to a pokemon api and parse the response into getStaticPaths at run to generate the static paths you want to use. I encourage you to play with the documentation more, that team did a killer job with them.
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
})
);
}
Here's an example for a posts table - but basically you get all the posts or in your case Pokemon, and map the names to an array.
I'm not sure if there's a better way of handling it but that should point you in the right direction. I'm still reading up on NextJS and especially the new Beta myself, so take with a pinch of salt.

How to access the props provided by getServerSideProps in the page?

I have a question regarding Next.js with GraphQL/Apollo and Typescript. A Next.js page I am trying to understand exports a getServerSideProps function. How can I access the props or rather the data that I fetch in getServerSideProps in the page?
I can see it when I log the props (see code), but it is in the object in a rather unusual format, with the id in the key. My IDE doesn't allow me to get to them via autocomplete; it only "sees" the normal prop graphQlClientId.
Bonus question: I am also unsure what the ctx which is passed into the getServerSideProps is and where it is coming from.
export interface PageProps {
graphQlClientId: string;
}
export default function Page(props: PageProps) {
const { graphQlClientId } = props;
console.log(props);
// {
// ...
// graphQlClientId: "12dpZ450LTsadsf",
// __APOLLO_STATE__: {
// ROOT_QUERY: {
// user({"id":"12dpZ450LTsadsf"}): {
// name: 'Carl C'
// }
// }
// }
// }
return (
<Layout graphQlClientId={graphQlClientId}>
<SomeComponent user={user} />
</Layout>
);
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const { apolloClient, clientId, authToken, csrfToken } = await getApolloClient(ctx);
const userDataRequest = apolloClient.query({
query: GetUserDocument,
variables: { clientId: clientId },
});
await userDataRequest.catch((err) => {
console.error(err);
});
return withApolloState({ apolloClient, authToken, clientId, csrfToken });
};
First the easy question, ctx is the context parameter for the getServerSideProps function. It contains the request object, the response object, route parameters, query parameters, and so on...
You use the context to create the initial data for the page i.e. you get a query parameter 'id' and query the database for that ID. Then you have that data available in pageProps.
See this page https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props for more detailed info.
The main question. When you return return withApolloState({ apolloClient, authToken, clientId, csrfToken }); you are sending this object { apolloClient, authToken, clientId, csrfToken } to the pageProps.
Here you are not setting a type, so you can't "see" what is there with your IDE. Also, is that the data that you want to use in your page?
To fix it, define a type and return an object of that type:
type T {
clientId string
authToken string
...
}
export const getServerSideProps: GetServerSideProps = async (ctx) : T => {
...
return { ... } as T;
}

sessionStorage not available immediately after navigate

I am trying to implement an React solution with Strapi as backend where authorization is done using JWT-keys. My login form is implemented using the function below:
const handleLogin = async (e) => {
let responsekey = null
e.preventDefault();
const data = {
identifier: LoginState.username,
password: LoginState.password
}
await http.post(`auth/local`, data).then((response) => {
setAuth({
userid: response.data.user.id,
loggedin: true
})
responsekey = response.data.jwt
setLoginState({...LoginState, success: true});
sessionStorage.setItem('product-authkey', responsekey);
navigate('/profile');
}).catch(function(error) {
let result = ErrorHandlerAPI(error);
setLoginState({...LoginState, errormessage: result, erroroccurred: true});
});
}
The API-handler should return an Axios item which can be used to query the API. That function is also shown below. If no API-key is present it should return an Axios object without one as for some functionality in the site no JWT-key is necessary.
const GetAPI = () => {
let result = null
console.log(sessionStorage.getItem("product-authkey"))
if (sessionStorage.getItem("product-authkey") === null) {
result = axios.create(
{
baseURL: localurl,
headers: {
'Content-type': 'application/json'
}
}
)
} else {
result = axios.create({
baseURL: localurl,
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${sessionStorage.getItem("product-authkey")}`
}
})
}
return result
}
export default GetAPI()
However, once the user is redirected to the profile page (on which an API-call is made which needs an JWT-key), the request fails as there is no key present in the sessionStorage. The console.log also shows 'null'. If I look at the DevTools I do see that the key is there... And if I refresh the profile page the request goes through with the key, so the key and backend are working as they should.
I tried making the GetAPI function to be synchronous and to move the navigate command out of the await part in the handleLogin function, but that didn't help.
Does someone have an idea?
Thanks!
Sincerely,
Jelle
UPDATE:
Seems to work now, but I need to introduce the getAPI in the useEffect hook, I am not sure if that is a good pattern. This is the code of the profile page:
useEffect(() => {
let testapi = GetAPI()
const getMatches = async () => {
const response = await testapi.get(`/profile/${auth.userid}`)
const rawdata = response.data.data
... etc
}, [setMatchState]
export default GetAPI() this is the problematic line. You are running the GetApi function when the module loads. Basically you only get the token when you visit the site and the js files are loaded. Then you keep working with null. When you reload the page it can load the token from the session storage.
The solution is to export the function and call it when you need to make an api call.

Requesting API data using getStaticProps() returns undefined object | Nextjs

I'm new to new to nextjs.
I'm making a real estate web using bayut API.
And got trouble requesting agencies data from the API. I want to show list of agencies on my page like so:
ss taken from bayut.com
index.js:
import { baseUrl, fetchApi } from "../utils/fetchApi";
export default function Agency({ agencyList }) {
console.log({ agencyList }); // <====I need this
return <div>aaa</div>;
}
export async function getStaticProps() {
//Fetch agencies list
const agencyList = await fetchApi(
`${baseUrl}/agencies/list?hitsPerPage=25&page=0&lang=en`
);
return {
props: {
agencyList: agencyList?.hits,
},
};
}
fetchApi.js:
import axios from "axios";
export const baseUrl = "https://bayut.p.rapidapi.com";
export const fetchApi = async (url) => {
const { data } = await axios.get(url, {
headers: {
"x-rapidapi-host": "bayut.p.rapidapi.com",
"x-rapidapi-key": process.env.NEXT_PUBLIC_RAPIDAPI_KEY,
},
});
return data;
};
In console returns:
{agencyList: undefined}agencyList:
undefined[[Prototype]]: Object
While It will return property object as it should if I change the url to:
/properties/list?locationExternalIDs=5002&purpose=for-sale&hitsPerPage=1
it will return:
agencyList: Array(1)}
agencyList: Array(1)
0: {id: 2828151, ownerID: 1101619, userExternalID: '1101619', sourceID: 1, state: 'active', …}
length: 1
I'm expecting to return similar kind of data as the property object. I also tried to copy the code from the API page but no luck:
2nd index.js:
import axios from "axios";
import React, { useState, useEffect } from "react";
const Agency = () => {
const [agentList, setAgentList] = useState([]);
const options = {
method: "GET",
url: "https://bayut.p.rapidapi.com/agencies/list",
params: { hitsPerPage: "25", page: "0", lang: "en" },
headers: {
"x-rapidapi-host": "bayut.p.rapidapi.com",
"x-rapidapi-key": process.env.NEXT_PUBLIC_RAPIDAPI_KEY,
},
};
const fetchAgentList = async () => {
const response = await axios.request(options);
setAgentList(response.data);
};
useEffect(() => {
fetchAgentList();
}, []);
useEffect(() => {
console.log(agentList);
}, [agentList]);
return <div>aaa</div>;
};
export default Agency;
It returns an empty array:
[]
length: 0
[Question 1]Any idea how to solve this? Am I missing any parameter? Also above code wont return any error message but, if I create a new page, named localhost:3000/agency, and copy the index.js code to agency.js, it will run to this error message:
Server Error
Error: Error serializing .agencyList returned from getStaticProps in "/agency".
Reason: undefined cannot be serialized as JSON. Please use null or omit this value.
Even requesting property object (which works on index.js) it won't work on agency.js page.[Question 2] Does it means I can only use getStaticProps() on / page only (why this happend)? or do I need to initialize something?
Sorry for messy post. first time posting. Thankyou!
tl;dr A real estate web requesting API data. getStaticProps() works on property object but not on agency object. what am I missing here?
Got it working, I just need to add query: '{some word}' into the parameter. It is stated on rapidApi page that the paramater is optional but I guess I couldn't make request without it.

NEXT.js ISR remove static page

I have a little problem with the ISR. I have the revalidate prop equal 1s like here
export async function getStaticProps({ params }) {
const data = await client.getEntries({
content_type: "product",
"fields.name": params.slug,
});
if (!data.items[0]) {
return {
notFound: true,
};
}
return {
props: {
article: data.items[0],
revalidate: 1,
},
};
}
When I create product in Contentful, the page is created as I expected. When I want to entry to page that doesn't exist I get 404 error as expected. Problem starts when I change something in Contentful in existing product or I delete it.
When I delete the product in Contentful, the list of products in the products page is updated and the product disappears but I can still entry in page of that product. Also when I rename the product name the list of products is updated but I still can entry to the earlier page name.
Is there any solution to solve this problem?
getStaticPaths
export async function getStaticPaths() {
const data = await client.getEntries({
content_type: "product",
});
return {
paths: data.items.map((item) => ({
params: { slug: item.fields.name },
})),
fallback: true,
};
}
Product page
const Article = ({ article }) => {
const router = useRouter();
if (router.isFallback) return <p>Loading...</p>;
return (
<div>
<h1>Hello! {article.fields.name}</h1>
<Link href="/about">
<a>Back to about!</a>
</Link>
</div>
);
};
EDIT
When I change product name from "product77" to "product7" in Contentful after revalidate static page in my build for product77 still exist and I still can entry to that page.
Shouldn't it be removed after revalidation?
Removed pages can't be deleted, but served with a 404 instead using On-demand Revalidation, if getStaticPaths and getStaticProps are configured accordingly.
// /pages/blog/[...post].jsx
function Post({ postData }) {
return (
// ...
);
}
export async function getStaticPaths() {
const res = await fetch("https://.../posts");
const posts = await res.json();
// Prerender paths during build
const paths = posts.map((post) => ({
params: { post: post.id },
}));
return { paths, fallback: "blocking" };
}
export async function getStaticProps(context) {
const res = await fetch(`https://.../posts/${context.params.post}`);
const postData = await res.json();
if(!postData){
// The page doesn't exist in the CMS, return notFound to trigger a 404
return{
notFound: true,
revalidate: 30
}
}
// Return page props
return {
props: {
postData,
},
revalidate: 30,
};
}
The main takeaway from the above code is:
You can pre-render paths in getStaticPaths
getStaticPaths must use "blocking" or true as the fallback value
Handle unknown paths inside getStaticProps (thanks to fallback). If the page doesn't exist in the CMS, return notFound: true triggering the 404 page
Then, using the On-demand revalidation approach in a webhook:
// /pages/api/revalidate-post.js
export async function handler(req, res) {
try {
const reqBody = JSON.parse(req.body);
await res.revalidate(`/${reqBody.path}`);
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send("Error revalidating");
}
}
The res.revalidate(/${reqBody.path}) invokes a fresh evaluation of a page using the logic from getStaticProps.
Now, if somebody would delete the page in the CMS, and trigger the above revalidation webhook handler for the deleted page path, then the path serves a 404 page instead of the original page.
The page itself, however, is not deleted from the disk.

Resources