Next.js: getStaticPaths for nested dynamic routes data structure error - javascript

I have a page [categories][price].js and im trying to achieve the data structure in getStaticPaths e.g
cat1/10 cat1/20 cat1/30 cat1/40 cat2/10 cat/20 etc
I have looked at this post: Next.js: getStaticPaths for nested dynamic routes as it's the same error but as their data structure is a bit different I'm not sure how to translate this to my example
It looks like im mapping the data incorrectly as I get the following error when trying to create my dynamic routes.
Error: Additional keys were returned from `getStaticPaths` in page "/[catSlug]/[price]". URL Parameters intended for this dynamic route must be nested under the `params` key, i.e.:
return { params: { catSlug: ..., price: ... } }
Keys that need to be moved: 0, 1.
How can I correctly map my data?
[categories][price].js
export async function getStaticPaths() {
const prices = [' 10', ' 20', ' 30']
const categories = [{ name: 'cat' }, { name: 'cat2' }, { name: 'cat3' }]
const paths = categories.map(({ slug }) =>
prices.map((price) => ({ params: { catSlug: slug, price: price } }))
)
return {
paths,
fallback: false
}
}

flatten the array
const paths = categories.map(({ name }) =>
prices.map((price) => ({ params: { catSlug: name, price: price } }))
).flat()

Related

How to get paths in getStaticPaths with locales in Next.js?

I'm using Strapi as a CMS, where I query for slugs, and I would like to have statically generated pages using getStaticPaths and getStaticProps in Next.js.
As I need to work with multiple locales, I have to map through the locales and get paths for each "Announcements" I'm getting from my query.
The error message I'm getting is:
Error: A required parameter (slug) was not provided as a string in getStaticPaths for /updates/announcements/[slug]
This is my getStaticPaths:
export async function getStaticPaths({ locales }: any) {
const paths = await (
await Promise.all(
locales.map(async (locale: any) => {
const { data } = await client.query({
query: gql`
query Announcements {
announcements(locale: "${locale}") {
data {
attributes {
slug
locale
}
}
}
}
`,
});
return {
announcements: data.announcements.data,
locale,
};
})
)
).reduce((acc, item) => {
item.announcements.map((p: any) => {
acc.push({
params: {
slug:
p.attributes.slug === "/" ? false : p.attributes.slug.split("/"),
},
locale: p.attributes.locale,
});
return p;
});
return acc;
}, []);
return {
paths,
fallback: false,
};
}
If I console.log(paths) I get the following in the terminal:
[
{ params: { slug: [Array] }, locale: 'en' },
{ params: { slug: [Array] }, locale: 'en' },
{ params: { slug: [Array] }, locale: 'en' },
{ params: { slug: [Array] }, locale: 'da' },
{ params: { slug: [Array] }, locale: 'sv' },
{ params: { slug: [Array] }, locale: 'nb' }
]
I might think that Next.js don't want the slug to be an array, but I'm not entirely sure. What am I doing wrong?
You page uses dynamic routes named (/updates/announcements/[slug]), therefore the param slug is required in paths.
From the Next.js getStaticPaths documentation:
The paths key determines which paths will be pre-rendered. For example, suppose that you have a page that uses Dynamic Routes named pages/posts/[id].js. If you export getStaticPaths from this page and return the following for paths:
return {
paths: [
{ params: { id: '1' }},
{
params: { id: '2' },
// with i18n configured the locale for the path can be returned as well
locale: "en",
},
],
fallback: ...
}
Then, Next.js will statically generate /posts/1 and /posts/2 during next build using the page component in pages/posts/[id].js.
The slug param can only be a string since it's used to generate routes. As you found when logging paths, you were trying to pass slug: [Array].
The problem in the question's code snippet is this expression to assign a slug:
// ...
params: {
slug: p.attributes.slug === "/" ? false : p.attributes.slug.split("/"), // 👈
},
// ...
This expression will either assign false (boolean) or an array of substrings (see the docs for String.prototype.split()).
In this case, as confirmed in a comment above, simply passing the slug as a string solves the issue.
The confusion likely came from following a tutorial that uses an optional catch-all route (pages/[[...slug]]) instead of regular dynamic routes (pages/[slug]) (ref).
From the Next.js getStaticPaths documentation again:
If the page name is pages/posts/[postId]/[commentId], then params should contain postId and commentId.
If the page name uses catch-all routes like pages/[...slug], then params should contain slug (which is an array). If this array is ['hello', 'world'], then Next.js will statically generate the page at /hello/world.
If the page uses an optional catch-all route, use null, [], undefined or false to render the root-most route. For example, if you supply slug: false for pages/[[...slug]], Next.js will statically generate the page /.

How to handle the JSON object which lack of some information?

I am using React with nextJS to do web developer,I want to render a list on my web page, the list information comes from the server(I use axios get function to get the information). However some JSON objects are lack of some information like the name, address and so on. My solution is to use a If- else to handle different kind of JSON object. Here is my code:
getPatientList(currentPage).then((res: any) => {
console.log("Response in ini: " , res);
//console.log(res[0].resource.name[0].given[0]);
const data: any = [];
res.map((patient: any) => {
if ("name" in patient.resource) {
let info = {
id: patient.resource.id,
//name:"test",
name: patient.resource.name[0].given[0],
birthDate: patient.resource.birthDate,
gender: patient.resource.gender,
};
data.push(info);
} else {
let info = {
id: patient.resource.id,
name: "Unknow",
//name: patient.resource.name[0].given[0],
birthDate: patient.resource.birthDate,
gender: patient.resource.gender,
};
data.push(info);
}
});
Is there any more clever of efficient way to solve this problem? I am new to TS and React
Use the conditional operator instead to alternate between the possible names. You should also return directly from the .map callback instead of pushing to an outside variable.
getPatientList(currentPage).then((res) => {
const mapped = res.map(({ resource }) => ({
id: resource.id,
// want to correct the spelling below?
name: "name" in resource ? resource.name[0].given[0] : "Unknow",
birthDate: resource.birthDate,
gender: resource.gender,
}));
// do other stuff with mapped
})

Prevent getStaticPaths running for specific locale

Is it possible that you prevent Next.js getStaticPaths for generating static pages for a specific locale?
In my next config:
i18n: {
locales: ['default', 'en', 'hu', 'de', 'cz', 'eu', 'sl'],
defaultLocale: 'default',
localeDetection: true,
},
That's just because we need the locale all the time, and by the default, Next.js does not support it, so we have to use the middleware trick followed by the official Next.js docs: https://github.com/vercel/next.js/discussions/18419
But now when I want to generate sites, I don't want to generate pages like
/default/products/id1
/default/products/id2
How can I prevent Next.js doing this? Because this does not work:
For my faq page where /help/[maincategory]/[subcategory]/[help].
Using slice (I will skip the default locale):
locales.slice(1).map((locale) => {
pages.map((item) => {
item.data.mainTabList?.[locale]?.map((main, mainidx) => {
main.subTabList?.map((sub, subidx) => {
questions.map((help) => {
help.data.helplist?.[locale]?.map((title) => {
const urllink = {
maincategory: urlConverter(main.tab),
subcategory: urlConverter(sub.tab),
help: title.url
}
routes.push(urllink)
})
})
})
})
})
})
const paths = routes.map((doc) => ({
params: {
maincategory: `${doc.maincategory}`,
subcategory: `${doc.subcategory}`,
help: doc.help?.toLowerCase(),
},
}))
Can anyone help me how to solve this, that /default pages won't get generated, because that's just a hacky way for my locale prefix, we won't use it anywhere.
It's possible to control which locales getStaticPaths will generate paths for by returning the desired locales in the paths array.
From the i18n Dynamic Routing documentation:
For pages using getStaticProps with Dynamic Routes, all locale
variants of the page desired to be prerendered need to be returned
from getStaticPaths. Along with the params object returned for paths,
you can also return a locale field specifying which locale you want to
render.
In your case, your getStaticPaths function should roughly look like the following.
export const getStaticPaths = ({ locales }) => {
// Your own logic
// Filter out the `default` locale, and map through the remaining locales
locales.filter((locale) => locale !== 'default').map((locale) => {
pages.map((item) => {
item.data.mainTabList?.[locale]?.map((main, mainidx) => {
main.subTabList?.map((sub, subidx) => {
questions.map((help) => {
help.data.helplist?.[locale]?.map((title) => {
const urlLink = {
maincategory: urlConverter(main.tab),
subcategory: urlConverter(sub.tab),
help: title.url,
locale // Also push current `locale` value to be used in `paths` array
}
routes.push(urlLink)
})
})
})
})
})
})
const paths = routes.map((doc) => ({
params: {
maincategory: `${doc.maincategory}`,
subcategory: `${doc.subcategory}`,
help: doc.help?.toLowerCase(),
},
locale: doc.locale // Pass `locale` value here
}))
return {
paths,
fallback: false
}
}

Angular 12 how to return an http get request array of objects and assign to other array of different type using map()

Hey I'm trying to implement a bootstrap5 dropdown following this example: Creating Multi-Select Dropdown with Angular and Bootstrap 5
In that example, to get the data, he uses an app.service and just returns an array of objects:
getFoods(): Food[] {
return [
{
id: 1,
name: 'Grapes'
},
{
id: 2,
name: 'Melon'
},
...
And then in his ngOnInit() calls the getFoods() method and also uses .map() operator because he has to assign to values because the item model has two values:
ngOnInit(): void {
this.items = this.appService.getFoods().map(fruit => ({
id: fruit.id,
name: fruit.name
} as Item));
}
So I'm trying to do hat but with data being fetched from an API endpoint using HTTP GET request.
But I don't know how to use the .map() operator for the http get request:
this.subscription = this.contactSearchService.currentCodes.pipe(
map(
code => (
{
id: code.code,
name: code.code
}
)
)).subscribe(Response => {
this.items = Response
})
It's giving me these errors:
Property 'code' does not exist on type 'ResponsibilityCode[]'.
Type '{ id: any; name: any; }' is missing the following properties from type 'Item[]': length, pop, push, concat, and 26 more.
My http get request function:
private _reponsibilityCodeSource = new BehaviorSubject<ResponsibilityCode[]>([]);
currentCodes = this._reponsibilityCodeSource.asObservable();
getCodes(): void {
this.http.get<ResponsibilityCode[]>('https://localhost:44316/api/SITEContacts/ResponsibilityCode').subscribe(Response => {
this._reponsibilityCodeSource.next(Response);
});
}
I get the data as `JSON` btw.
The rxjs pipe(map(.... code...)) is different than array.map -
pipe(map()) does not operate on each item of an array
So the errors you are getting is because you're swapping out the array of ResponsibilityCode for a single item (code in your code is all the responsibility codes)
Try
this.subscription = this.contactSearchService.currentCodes.subscribe(Response => {
this.items = Response.map(
code => (
{
id: code.code,
name: code.code
}
)
)
})
Your HTTP get returns an Observable of ResponsibilityCode array, so to achieve that you have to map (Array.prototype.map) the items of the array within the RxJS's map operator, like the following:
this.subscription = this.contactSearchService.currentCodes
.pipe(
map((res: ResponsibilityCode[]) =>
res.map((item) => ({
id: item.id,
name: item.name,
}))
)
)
.subscribe((res) => {
this.items = res;
});

How to create dynamic URL's in Gatsby from term relationships in Drupal JSON:API response?

I'm Using Drupal 8 as a headless CMS with Gatsby generating static pages.
In Drupal I have set up some node Types (Article, Image, other...). All entities have brand and category term relationships.
category is a single selection and brand is a multiple selection
Gatsby creates pages from my template on URL's like.
http://example.com/category-1/brand-1/
http://example.com/category-1/brand-2/
http://example.com/category-2/brand-1/
http://example.com/category-2/brand-2/
The articles and image nodes with the terms selected will display on the corresponding url's.
The problem I'm having is since I changed the brand term to a multi selection, some URL's are not being created.
Because of field_brand[0].name Gatsby will only create the URL for the first selection of brand on the article node.
// gatsby-node.js
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions;
const pageTemplate = path.resolve(`src/templates/pageTemplate.js`);
return graphql(`
{
taxonomyTermBrand {
field_colours
name
}
allNodeImage {
nodes {
relationships {
field_image_drop {
uri {
url
}
}
}
}
}
allNodeArticle {
nodes {
body {
processed
}
relationships {
field_brand {
name
}
field_category {
name
}
}
}
}
}
`, { limit: 1 }).then(result => {
if (result.errors) {
throw result.errors
}
result.data.allNodeArticle.nodes.forEach(node => {
const brand_path = node.relationships.field_brand[0].name;
const category_path = node.relationships.field_category.name;
createPage({
path: `${category_path}/${brand_path}`,
component: pageTemplate,
context: {
brandName: node.relationships.field_brand[0].name,
categoryName: node.relationships.field_category.name,
},
})
})
})
}
Essentially the value of taxonomyTermBrand.name is the same as node.relationships.field_brand[0].name when the data is passed to the template, but I can't use taxonomyTermBrand.name because the path in createPage is in the allNodeArticle.forEach()
Is there a better aproach or another way to set the paths and display the tagged content on those pages?
I think you'd be able to do a forEach over each of the field_brand terms for each of the articles.
Something like:
result.data.allNodeArticle.nodes.forEach(node => {
const brands = node.relationships.field_brand;
const category = node.relationships.field_category.name;
brands.forEach(brand => {
createPage({
path: `${category}/${brand.name}`,
component: pageTemplate,
context: {
brandName: brand,
categoryName: category,
},
})
});
})

Categories