Unable to fetch data onto a dynamic page in Next JS - javascript

I am trying to create a dynamic page in Nextjs app, but continue to get an error:
./somepage/[id].js (20:25) # map
Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading 'map')
So basically what I did to recreate the issue was
I set up a dynamic page [id].js and tried fetching data from a helper file using getStaticProps and getStaticPaths as shown below:
// "./somepage/[id].js"
import {postData} from "./lib/helper"
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
export default function Post({ posts }) {
console.log(posts); //is "undefined".
console.log(postData) // logs out the data successfully
return (
<>
{posts.map((post) =>{
<div key = {post.id}>
Hello {post.author}, This is your title: {post.title}
</div>
})}
</>
);
}
export async function getStaticPaths({ params }) {
const { id } = params;
const posts = postData(id);
const paths = posts.map((item) => {
params: {
id: item.id.toString();
}
return {
paths,
fallback: false,
};
});
}
export async function getStaticProps() {
const posts = postData();
console.log(posts); //Logs nothing to the console
return {
props: { posts }, //This prop `posts` renders as undefined on the page.
};
}
The helper file is a function that returns an array of objects, and filters through the array if the array id matches the id from the params as shown below:
// "./lib/helper.js"
export function postData(id) {
const data = [{
...
...
}]
if(id){
return data.filter((item) => {
return item.id === id;
});
}
return data;
}
The issue I am having is that if I console log data from the helper file from within getStaticProps as shown above, I don't get anything back in the console... nothing, but get the above error in the browser. However, logging postData (from the helper.js file) inside of the dynamic page itself returns data as expected, but logging the prop posts that I passed in the getStaticProps comes out as undefined . My version of NextJs is v.13.. could the version be the reason why i couldn't pass data as props onto the page?

For me it is supposed to be something like that :
export async function getStaticPaths() {
const posts = postData();
const paths = posts.map((item) => {
params: {
id: item.id.toString();
}
});
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
const { id } = params;
const posts = postData(id);
return {
props: { posts },
};
}

Related

How do I refetch data after specific event triggered (click a button) using SWR, React Hooks for Data Fetching [duplicate]

This component is for counting views at page level in Next.js app deployed on AWS Lambda
function ViewsCounter({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.catch(console.log);
registerView();
}, [slug]);
return (
<>
{views}
</>
);
}
This one is for displaying views on homepage
function ViewsDisplay({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
return (
<>
{views}
</>
);
}
While it works as expected on localhost, looks like it displays only the first fetched value and doesn't revalidate it for some reason.
When visiting the page, Counter is triggered correctly and the value is changed in DB.
Probably it has something to do with mutating, any hints are appreciated.
useSWR won't automatically refetch data by default.
You can either enable automatic refetch using the refreshInterval option.
const { data } = useSWR(`/api/views/${slug}`, fetcher, { refreshInterval: 1000 });
Or explicitly update the data yourself using a mutation after the POST request to the API.
function ViewsCounter({ slug }: { slug: string }) {
const { data, mutate } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.then(() => {
mutate();
})
.catch(console.log);
registerView();
}, [slug]);
return (<>{views}</>);
}

Next.js Dynamic Path - Can't get context.params query

I am trying to set up a dynamic page in Next.js with getStaticPaths(). I have it 90% of the way there, but I can't get the getStaticProps() function to reference the URL query so that it loads the proper info.
My code is:
//Get Static Paths
export async function getStaticPaths() {
const paths = await (
await youtube.get("search?")
).data.items.map((video: any) => {
const id = video.id.videoId;
return { params: { id } };
});
return {
paths: paths.map((path: any) => path),
fallback: false,
};
}
//Get Static Props
export async function getStaticProps(context: any) {
const { query = "" } = context.params.query;
const videos = await (await youtube.get("search?")).data.items;
const video = videos.find((vid: any) => {
return vid.id.videoId === query;
});
return {
props: {
video,
},
revalidate: 10,
};
}
I am receiving the error:
Error: Error serializing `.video` returned from `getStaticProps` in "/media/videos/[id]".
Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit this value.
If I replace the 'query' variable in the getStaticProps() function with the actual ID of the video I am trying to load, everything goes through fine and the page loads. But then this is not dynamic, and is not using the url query to match with the proper video like I want.
Here is the working, non-dynamic code with a manually typed in ID to force a match.
//Get Static Props
export async function getStaticProps(context: any) {
// It's important to default the slug so that it doesn't return "undefined"
const { query = "" } = context.params;
const videos = await (await youtube.get("search?")).data.items;
const video = videos.find((vid: any) => {
return vid.id.videoId === "_TACZtT1irI";
});
return {
props: {
video,
},
revalidate: 10,
};
}
How can I make sure that the 'video' variable is accessing the url query? It should be grabbing it with context.params, but that is not working, what am I doing wrong?
I figured it out. This is working.
//Get Static Props
export async function getStaticProps(context: any) {
// It's important to default the slug so that it doesn't return "undefined"
const { query = "" } = context.params;
const videos = await (await youtube.get("search?")).data.items;
const video = videos.find((vid: any) => {
return vid.id.videoId === context.params.id;
});
return {
props: {
video,
},
revalidate: 10,
};
}

How can I generate a separate NextJS page for each FaunaDB Document?

How can I generate a different title on every page within a sub-directory?
My code throws no errors, but unfortunately the Title component renders every title-item on every page that it is supposed to, e.g. every app.com/title/<title> renders the same view ( a list of titles). I think that getStaticPaths is correctly parameterised, but I don't think that getStaticProps is.
export default function Title({ paper }) {
// paper is just the entire dataset, and isn't split by id or author etc.
return (
<div>
{paper.map(paper => (
<h1>{paper.data.title}</h1>
))}
</div>
)
}
export async function getStaticProps({ params }) {
// ideally, results should be split down to e.g. `/api/getPapers/title`, but this didn't work
const results = await fetch(`http://localhost:3000/api/getPapers/`).then(res => res.json());
return {
props: {
paper: results
}
}
}
export async function getStaticPaths() {
const papers = await fetch('http://localhost:3000/api/getPapers').then(res => res.json());
const paths = papers.map(paper => {
return {
params: {
authors: paper.data.title.toLowerCase().replace(/ /g, '-')
}
}
})
return {
paths,
fallback: false
}
}
This is the getPapers API function.
const faunadb = require("faunadb");
// your secret hash
const secret = process.env.FAUNADB_SECRET_KEY;
const q = faunadb.query;
const client = new faunadb.Client({ secret });
module.exports = async (req, res) => {
try {
const dbs = await client.query(
q.Map(
// iterate each item in result
q.Paginate(
// make paginatable
q.Match(
// query index
q.Index("all_research_papers") // specify source
)
),
(ref) => q.Get(ref) // lookup each result by its reference
)
);
// ok
res.status(200).json(dbs.data);
} catch (e) {
// something went wrong
res.status(500).json({ error: e.message });
}
};
My attempts to render a separate page for each document were missing a dynamic API call. I was simply hoping to render dynamic pages with a single (batched-document) API call.
Here is a typical dynamic API route called [index.js]:
const faunadb = require('faunadb')
// your secret hash
const secret = process.env.FAUNADB_SECRET_KEY
const q = faunadb.query
const client = new faunadb.Client({ secret })
export default async (req, res) => {
const {
query: { index },
} = req;
try {
const papers = await client.query(
q.Get(q.Ref(q.Collection('<name of the collection>'), index))
);
res.status(200).json(papers.data);
} catch (e) {
res.status(500).json({ error: e.message });
}
};
Once your data is being retrieved dynamically, you can set up a dynamic page route, e.g. [id].js, that fetches the data using useSWR.
const { data, error } = useSWR(`/api/getPapers/${id}`, fetcher);
This is an example fetcher function:
const fetcher = (url) => fetch(url).then((r) => r.json());
In my case, I could then retrieve any given field using the call {data.<field>}.
You are returning authors in your Path object. You will need to make sure that your page file is named with authors included. For example:
app_directory
|- pages
|- home.js
|- title
|- [authors].js
Perhaps where you say authors in your params object, you do mean title. In which case, rename the params object and page filename.
const paths = papers.map(paper => {
return {
params: {
title: paper.data.title.toLowerCase().replace(/ /g, '-')
}
}
})
app_directory
|- pages
|- home.js
|- title
|- [title].js
Here are the docs for getStaticPaths(). https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation
EDIT:
Since your API function returns the Page from your query, the shape of the result will likely be
{
before: [/* before cursor */],
after: [/* after cursor */],
data: [
{ /* paper Document */ },
{ /* paper Document */ },
{ /* paper Document */ },
]
}
In which case, your code will need to map over papers.data not on papers itself.
const paths = papers.data // select the data
.map(paper => {
return {
params: {
title: paper.data.title.toLowerCase().replace(/ /g, '-')
}
}
})

How to access route parameter inside getServerSideProps in Next.js?

I want to query my Supabase table using the ID in the slug e.g. localhost:3000/book/1 then show information about that book on the page in Next.js.
Table
book/[id].js
import { useRouter } from 'next/router'
import { getBook } from '#/utils/supabase-client';
export default function Book({bookJson}) {
const router = useRouter()
const { id } = router.query
return <div>
<p>Book: {id}</p>
<h1>{bookJson}</h1>
</div>
}
export async function getServerSideProps(query) {
const id = 1 // Get ID from slug
const book = await getBook(id);
const bookJson = JSON.stringify(book)
return {
props: {
bookJson
}
};
}
utils/supabase-client.js
export const getBook = async (id) => {
const bookString = id
let bookId = parseInt(bookString);
const { data, error } = await supabase
.from('books')
.select('id, name')
.eq('id', bookId)
if (error) {
console.log(error.message);
throw error;
}
return data || [];
};
As described in getServerSideProps documentation, you can access the route parameters through the getServerSideProps's context, using the params field.
params: If this page uses a dynamic route, params contains the route parameters. If the page name is [id].js, then params will look like { id: ... }.
export async function getServerSideProps(context) {
const id = context.params.id // Get ID from slug `/book/1`
// Rest of `getServerSideProps` code
}
Alternatively, you can also use the query field to access the route parameters. The difference is that query will also contain any query parameter passed in the URL.
export async function getServerSideProps(context) {
const id = context.query.id // Get ID from slug `/book/1`
// If routing to `/book/1?name=some-book`
console.log(context.query) // Outputs: `{ id: '1', name: 'some-book' }`
// ...
}

How to avoid "id undefined" in Next.js dynamic route?

I am going from one page to another with router :
const goToArticle = (index) => {
router.push({
pathname: `/article/${index}`,
})
}
The page I am going to is named as [id].js and is exported as Article
And in the article page I am trying to get that index :
export async function getStaticPaths({id}) {
const res = await fetch('myurl');
const articles = await res.json();
console.log(params.id);
return {
props: {
article: articles.items[id]
}
}
}
Error : ID is undefined
TypeError: Cannot destructure property 'id' of 'undefined' as it is
undefined.
How to get that index and return array item with that index ?
You can change function redirection as below:
const goToArticle = (index) => {
router.push({
pathname: `/article/[id]`,
as: `/article/${index}`,
})
}
please refer document: https://nextjs.org/docs/api-reference/next/router

Categories