nextjs nested dynamic route error 'a required parameter was not provided' - javascript

Error: A required parameter (plantName) was not provided as a string in getStaticPaths for /plants/[plantName]/streaming-data/[panel]
above is the error showing up.My folder structure is plants > [plantName] > streamin-data > [panel]
I have defined getStaticPaths inside of [plantName] using statically available list of plant names. And also tried doing the same in [panel]
For example two panels are there "a" and "b" and one plantname, so inside of [plant] slug file i created static paths like plantname/a and plantname/b but still the error came up.
// [panel].js
export async function getStaticPaths() {
const paths = [];
const listOfPlants = plantsList.map((plant) => ({
plantName: plant.id,
}));
const listOfPanels = [
'heat-transfer',
'asset-tags',
'test-tags',
'flow-meter-board',
'pi-data',
'all-tags',
'analytics',
'manual-logs',
'data-quality',
];
listOfPlants.forEach((plantName) => {
listOfPanels.forEach((panel) => {
paths.push({ params: { plantName, panel } });
});
});
return {
paths,
fallback: false, // can also be true or 'blocking'
};
}
export const getStaticProps = async ({ params }) => ({
props: {
panel: params.panel,
plantName: params.plantName,
},
});
// [plantName].js
export const getStaticPaths = async () => ({
paths: (() => {
const plants = plantsList;
const params = plants.map((plant) => ({ params: { plantName: plant.id } }));
return params;
})(),
fallback: true,
});
export const getStaticProps = async ({ params }) => ({
props: {
panel: params.plantName,
},
});

It seems like the plantName value in your paths array is not a string.
listOfPlants is an array of objects, so when you map over it you need to destructure each item to access plantName, like so:
listOfPlants.forEach(({ plantName }) => {

Related

Error: A required parameter (slug) was not provided as an array in getStaticPaths for /posts/[...slug]

I have a problem with the 'getStaticPaths' function. When I try to get a dynamic display with a parameter it shows me as error: Error: A required parameter (slug) was not provided as an array in getStaticPaths for /posts/[...slug]
In My utils.js file got this function read the files
export function getPostsFiles() {
const postsDirectory = path.join(process.cwd(), "posts");
return fs.readdirSync(postsDirectory);
}
In my page [...slug]
export function getStaticProps(context) {
const { params } = context;
const { slug } = params;
const postData = getPostData(slug);
return {
props: {
post: postData,
},
revalidate: 600,
};
}
export function getStaticPaths() {
const postFilenames = getPostsFiles();
const slugs = postFilenames.map((fileName) => fileName.replace(/\.md$/, ""));
const parameters = slugs.map((slug) => ({ params: { slug: slug } }));
return {
paths: parameters,
fallback: true,
};
}

When routing mswjs/data populates the database with new items and removes the previous one, making it inaccessible

I use next-redux-wrapper, MSW, #mswjs/data and redux-toolkit for storing my data in a store as well as mocking API calls and fetching from a mock Database.
I have the following scenario happening to me.
I am on page /content/editor and in the console and terminal, I can see the data was fetched from the mock database and hydrated from getStaticProps of Editor.js. So now IDs 1 to 6 are inside the store accessible.
Now I click on the PLUS icon to create a new project. I fill out the dialog and press "SAVE". a POST request starts, it's pending and then it gets fulfilled. The new project is now in the mock DB as well as in the store, I can see IDs 1 to 7 now.
Since I clicked "SAVE" and the POST request was successful, I am being routed to /content/editor/7 to view the newly created project.
Now I am on Page [id].js, which also fetched data from the mock DB and then it gets stored and hydrated into the redux store. The idea is, it takes the previous store's state and spreads it into the store, with the new data (if there are any).
Now the ID 7 no longer exists. And IDs 1 to 6 also don't exist anymore, instead, I can see in the console and terminal that IDs 8 to 13 were created, and the previous ones are no more.
Obviously, this is not great. When I create a new project and then switch the route, I should be able to access the newly created project as well as the previously created ones. But instead, they all get overwritten.
It either has something to do with the next-redux-wrapper or MSW, but I am not sure how to make it work. I need help with it. I will post some code now:
Code
getStaticProps
// path example: /content/editor
// Editor.js
export const getStaticProps = wrapper.getStaticProps(
(store) =>
async ({ locale }) => {
const [translation] = await Promise.all([
serverSideTranslations(locale, ['editor', 'common', 'thesis']),
store.dispatch(fetchProjects()),
store.dispatch(fetchBuildingBlocks()),
]);
return {
props: {
...translation,
},
};
}
);
// path example: /content/editor/2
// [id].js
export const getStaticProps = wrapper.getStaticProps(
(store) =>
async ({ locale, params }) => {
const { id } = params;
const [translation] = await Promise.all([
serverSideTranslations(locale, ['editor', 'common', 'thesis']),
store.dispatch(fetchProjects()),
// store.dispatch(fetchProjectById(id)), // issue: fetching by ID returns null
store.dispatch(fetchBuildingBlocks()),
]);
return {
props: {
...translation,
id,
},
};
}
);
Mock Database
Factory
I am going to shorten the code to the relevant bits. I will remove properties for a project, as well es helper functions to generate data.
const asscendingId = (() => {
let id = 1;
return () => id++;
})();
const isDevelopment =
process.env.NODE_ENV === 'development' || process.env.STORYBOOK || false;
export const projectFactory = () => {
return {
id: primaryKey(isDevelopment ? asscendingId : nanoid),
name: String,
// ... other properties
}
};
export const createProject = (data) => {
return {
name: data.name,
createdAt: getUnixTime(new Date()),
...data,
};
};
/**
* Create initial set of tasks
*/
export function generateMockProjects(amount) {
const projects = [];
for (let i = amount; i >= 0; i--) {
const project = createProject({
name: faker.lorem.sentence(faker.datatype.number({ min: 1, max: 5 })),
dueDate: date(),
fontFamily: getRandomFontFamily(),
pageMargins: getRandomPageMargins(),
textAlign: getRandomTextAlign(),
pageNumberPosition: getRandomPageNumberPosition(),
...createWordsCounter(),
});
projects.push(project);
}
return projects;
}
API Handler
I will shorten this one to GET and POST requests only.
import { db } from '../../db';
export const projectsHandlers = (delay = 0) => {
return [
rest.get('https://my.backend/mock/projects', getAllProjects(delay)),
rest.get('https://my.backend/mock/projects/:id', getProjectById(delay)),
rest.get('https://my.backend/mock/projectsNames', getProjectsNames(delay)),
rest.get(
'https://my.backend/mock/projects/name/:id',
getProjectsNamesById(delay)
),
rest.post('https://my.backend/mock/projects', postProject(delay)),
rest.patch(
'https://my.backend/mock/projects/:id',
updateProjectById(delay)
),
];
};
function getAllProjects(delay) {
return (request, response, context) => {
const projects = db.project.getAll();
return response(context.delay(delay), context.json(projects));
};
}
function postProject(delay) {
return (request, response, context) => {
const { body } = request;
if (body.content === 'error') {
return response(
context.delay(delay),
context.status(500),
context.json('Server error saving this project')
);
}
const now = getUnixTime(new Date());
const project = db.project.create({
...body,
createdAt: now,
maxWords: 10_000,
minWords: 7000,
targetWords: 8500,
potentialWords: 1500,
currentWords: 0,
});
return response(context.delay(delay), context.json(project));
};
}
// all handlers
import { buildingBlocksHandlers } from './api/buildingblocks';
import { checklistHandlers } from './api/checklist';
import { paragraphsHandlers } from './api/paragraphs';
import { projectsHandlers } from './api/projects';
import { tasksHandlers } from './api/tasks';
const ARTIFICIAL_DELAY_MS = 2000;
export const handlers = [
...tasksHandlers(ARTIFICIAL_DELAY_MS),
...checklistHandlers(ARTIFICIAL_DELAY_MS),
...projectsHandlers(ARTIFICIAL_DELAY_MS),
...buildingBlocksHandlers(ARTIFICIAL_DELAY_MS),
...paragraphsHandlers(ARTIFICIAL_DELAY_MS),
];
// database
import { factory } from '#mswjs/data';
import {
buildingBlockFactory,
generateMockBuildingBlocks,
} from './factory/buildingblocks.factory';
import {
checklistFactory,
generateMockChecklist,
} from './factory/checklist.factory';
import { paragraphFactory } from './factory/paragraph.factory';
import {
projectFactory,
generateMockProjects,
} from './factory/project.factory';
import { taskFactory, generateMockTasks } from './factory/task.factory';
export const db = factory({
task: taskFactory(),
checklist: checklistFactory(),
project: projectFactory(),
buildingBlock: buildingBlockFactory(),
paragraph: paragraphFactory(),
});
generateMockProjects(5).map((project) => db.project.create(project));
const projectIds = db.project.getAll().map((project) => project.id);
generateMockTasks(20, projectIds).map((task) => db.task.create(task));
generateMockBuildingBlocks(10, projectIds).map((block) =>
db.buildingBlock.create(block)
);
const taskIds = db.task.getAll().map((task) => task.id);
generateMockChecklist(20, taskIds).map((item) => db.checklist.create(item));
Project Slice
I will shorten this one as well to the relevant snippets.
// projects.slice.js
import {
createAsyncThunk,
createEntityAdapter,
createSelector,
createSlice,
current,
} from '#reduxjs/toolkit';
import { client } from 'mocks/client';
import { HYDRATE } from 'next-redux-wrapper';
const projectsAdapter = createEntityAdapter();
const initialState = projectsAdapter.getInitialState({
status: 'idle',
filter: { type: null, value: null },
statuses: {},
});
export const fetchProjects = createAsyncThunk(
'projects/fetchProjects',
async () => {
const response = await client.get('https://my.backend/mock/projects');
return response.data;
}
);
export const saveNewProject = createAsyncThunk(
'projects/saveNewProject',
async (data) => {
const response = await client.post('https://my.backend/mock/projects', {
...data,
});
return response.data;
}
);
export const projectSlice = createSlice({
name: 'projects',
initialState,
reducers: {
// irrelevant reducers....
},
extraReducers: (builder) => {
builder
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.log('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
return {
...state,
...action.payload.projects,
statuses,
};
})
.addCase(fetchProjects.pending, (state, action) => {
state.status = 'loading';
})
.addCase(fetchProjects.fulfilled, (state, action) => {
projectsAdapter.addMany(state, action.payload);
state.status = 'idle';
action.payload.forEach((item) => {
state.statuses[item.id] = 'idle';
});
})
.addCase(saveNewProject.pending, (state, action) => {
console.log('SAVE NEW PROJECT PENDING', action);
})
.addCase(saveNewProject.fulfilled, (state, action) => {
projectsAdapter.addOne(state, action.payload);
console.group('SAVE NEW PROJECT FULFILLED');
console.log(current(state));
console.log(action);
console.groupEnd();
state.statuses[action.payload.id] = 'idle';
})
// other irrelevant reducers...
},
});
This should be all the relevant code. If you have questions, please ask them and I will try to answer them.
I have changed how the state gets hydrated, so I turned this code:
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.log('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
return {
...state,
...action.payload.projects,
statuses,
};
})
Into this code:
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.group('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
state.statuses = { ...state.statuses, ...statuses };
projectsAdapter.upsertMany(state, action.payload.projects.entities);
})
I used the adapter to upsert all entries.

ReactJS - How to set useState variable to the return value of a function

So what I have is a function, fillSidebarData(), that gathers and structures data for me and when done just returns it. What I want to do is set the variable "sidebarData" to the return value of this function. For reference the data looks like this, if that matters:
SidebarData = [
{
title: 'Overview',
path: '/overview',
subNav: [
{
title: 'Users',
path: '/users',
},
{
title: 'Revenue',
path: '/revenue',
}
]
}
Here is the code for the functions that creates the data.
const [sidebarData, setSidebarData] = useState([])
function fillSidebarData () {
let sidebarData = []
UserService.getPosts(0).then((response) => {
response.data.map((value, key) => {
UserService.getPosts(value.PostID).then((response) => {
let subNav = []
response.data.map((value, key) => {
subNav.push({
title : value.postName,
path: `/${value.PostID}`
})
})
let sidebarItem = {
title: value.postName,
path: `/${value.PostID}`,
subNav : subNav
}
sidebarData.push(sidebarItem)
})
})
//The console log shows the data in the exact format I want it, so nothing weird here
console.log(sidebarData)
return sidebarData
}, error => {
console.log(error)
})
}
useEffect(() => {
//Here is where I want to set the sidebar data to the return value of fillSideBarData method but sidebarData is undefined when using it elsewhere in the code.
setSidebarData(fillSidebarData())
return () => {
setSidebarData([])
}
}, [sidebarData])
Because your question is lacking some context, I will answer your question based on my assumption that you are trying to fetch the nav data from your backend and render your nav component based on the data you fetched.
I am noticing here that useEffect hook is not used within the function component. According to React documentation, you use Effect Hook to perform side effects in function component.
To answer your original question, your setSidebarData needs to be called within fillSidebarData function.
const Nav = () => {
const [sidebarData, setSidebarData] = useState([])
const fillSidebarData = () => {
let sidebarData = []
UserService.getPosts(0).then((response) => {
response.data.map((value, key) => {
UserService.getPosts(value.PostID).then((response) => {
let subNav = []
response.data.map((value, key) => {
subNav.push({
title : value.postName,
path: `/${value.PostID}`
})
})
let sidebarItem = {
title: value.postName,
path: `/${value.PostID}`,
subNav: subNav
}
sidebarData.push(sidebarItem)
})
})
// Your setSidebarData needs to be called here
setSidebarData(sidebarData)
})
.catch(error => {
console.log(error)
})
}
useEffect(() => {
// If you are updating your menu data just once,
// you probably do not need to resubscribe to your sidebarData state.
fillSidebarData()
})
return (
...
)
}

how to create multiple pages using different templates in 'gatsby-node'?

i'm new to gatsby and trying to programatically create pages using different templates, but I'm struggling with that.
I have two different pages (landing & press), both written in mdx files, that need different routes and templates when being created programmatically.
my landing works good, but i failed to add the "press" page. everything i tried didn't work.
for information, i have a templateKey 'press' and 'landing' in each frontmatter of my markdown files.
Here is my gatsby-node file :
const path = require("path");
const { createFilePath } = require("gatsby-source-filesystem");
const { fmImagesToRelative } = require("gatsby-remark-relative-images");
exports.createPages = ({ actions, graphql }) => {
const { createPage } = actions;
const landingTemplate = path.resolve("src/templates/landing.js");
// const pressTemplate = path.resolve("./src/templates/press.js");
return graphql(`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
id
fields {
slug
}
frontmatter {
templateKey
locale
}
}
}
}
}
`).then((result) => {
if (result.errors) {
result.errors.forEach((e) => console.error(e.toString()));
return Promise.reject(result.errors);
}
const posts = result.data.allMarkdownRemark.edges;
posts.forEach((edge) => {
const id = edge.node.id;
createPage({
path: edge.node.fields.slug,
component: landingTemplate,
context: {
locale: edge.node.frontmatter.locale,
id,
},
});
});
});
};
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
fmImagesToRelative(node); // convert image paths for gatsby images
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode });
createNodeField({
name: `slug`,
node,
value,
});
}
};
here is my query in graphiQL
Thank you in advance for any assistance that you may be able to give me !
You have to make two different queries to get all your press and all landing data (filtering by each templateKey). Once you have the different populated objects you need to call for each object the createPage API to create pages and pass each template value. It should look like:
const path = require("path");
const { createFilePath } = require("gatsby-source-filesystem");
const { fmImagesToRelative } = require("gatsby-remark-relative-images");
exports.createPages = async ({ actions, graphql }) => {
const { createPage } = actions;
const landingTemplate = path.resolve("src/templates/landing.js");
const pressTemplate = path.resolve("./src/templates/press.js");
const postQuery = await graphql(`
{
allMarkdownRemark(
limit: 1000
filter: { frontmatter: { templateKey: { eq: "press" }}},
) {
edges {
node {
id
fields {
slug
}
frontmatter {
templateKey
locale
}
}
}
}
}
`).then((result) => {
if (result.errors) {
result.errors.forEach((e) => console.error(e.toString()));
return Promise.reject(result.errors);
}
const posts = postQuery.data.allMarkdownRemark.edges;
posts.forEach((edge) => {
const id = edge.node.id;
createPage({
path: edge.node.fields.slug,
component: pressTemplate,
context: {
locale: edge.node.frontmatter.locale,
id,
},
});
});
});
const landingQuery = await graphql(`
{
allMarkdownRemark(
limit: 1000
filter: { frontmatter: { templateKey: { eq: "landing" }}},
) {
edges {
node {
id
fields {
slug
}
frontmatter {
templateKey
locale
}
}
}
}
}
`).then((result) => {
if (result.errors) {
result.errors.forEach((e) => console.error(e.toString()));
return Promise.reject(result.errors);
}
const landings = landingQuery.data.allMarkdownRemark.edges;
landings.forEach((edge) => {
const id = edge.node.id;
createPage({
path: edge.node.fields.slug,
component: landingTemplate,
context: {
locale: edge.node.frontmatter.locale,
id,
},
});
});
});
};
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
fmImagesToRelative(node); // convert image paths for gatsby images
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode });
createNodeField({
name: `slug`,
node,
value,
});
}
};
Your mistake was that you were returning your GraphQL query and that limit your capability to create new queries. Note that I've also added a promise-based approach, with an async and await functions in order to optimize the queries and the timings.
Without knowing your data structure I've duplicated the query changing the templateKey value, of course, you will need to customize the data that you want to gather for each template and the data you want to pass via context API.
So I created a method based on a netlify CMS starter, which it looks like you attempted as well, that makes sure you only make the query once:
exports.createPages = async ({graphql,actions }) => {
const { data } = await graphql(`
query Pages {
allMarkdownRemark {
nodes {
frontmatter {
slug
template
}
}
}
}
`)
data.allMarkdownRemark.nodes.forEach(node => {
actions.createPage({
path: `${node.frontmatter.slug}`,
component: path.resolve(
`./src/templates/${String(node.frontmatter.template)}.js`
),
context: {
slug: node.frontmatter.slug,
},
})
})
}
Then in the template it self you can call this query:
export const aboutPageQuery = graphql`
query AboutPage($slug: String) {
markdownRemark(frontmatter: { slug: { eq: $slug } }) {
frontmatter {
title
story {
text
title
}
}
}
}
`
Note: I'm still working through a method to include templates based on path. Say if you had a blog directory within pages that you wanted to apply a single template (blog-page.js) and custom path (/blog/{slug}).

Getting "A required parameter (id) was not provided as a string in getStaticPaths" error in Next.js

I have a problem with the getStaticPaths function.
When I try to get a dynamic display with a parameter it shows me as error: A required parameter (id) was not provided as a string in getStaticPaths for / movies / [id] but if I use the other way above it works. Above all I am the documentation.
import fetch from 'node-fetch';
function MovieSelect({movie}){
return(
<div>
<h1>Test: {movie.name}</h1>
<p>{movie.summary.replace(/<[/]?[pb]>/g, '')}</p>
{movie.image ? <img src={movie.image.medium} /> : null}
</div>
)
}
export async function getStaticPaths(){
const request = await fetch('https://api.tvmaze.com/search/shows?q=batman')
const movies = await request.json()
//const paths = movies.map(movie =>`/movies/${movie.show.id}`)
const paths = movies.map(movie =>({
params: {id: movie.show.id},
}))
return {
paths,
fallback: false
}
}
export async function getStaticProps({params}){
const request = await fetch(`https://api.tvmaze.com/shows/${params.id}`)
const movie = await request.json()
return{
props:{
movie
}
}
}
export default MovieSelect
A required parameter (id) was not provided as a string in getStaticPaths for / movies / [id]
id should be a string as suggested by the error. Upon hitting the api from your browser, you can see that the id is not a string but a number. You need to convert it to string.
params: {id: movie.show.id.toString()},
My problem generated the same error, but I had a different bug.
TL;DR: The name of my file needed to match the key of the slug used in the params object.
In my case, my file name was [postSlug].js. Therefore, the key should have been postSlug inside of getStaticPaths().
// In [postSlug].js
const pathsWithParams = slugs.map((slugs) => ({ params: { postSlug: slug } })); // <-- postSlug is right
const pathsWithParams = slugs.map((slugs) => ({ params: { id: slug } })); // <--- id is wrong
My entire function then looked like this
export async function getStaticPaths() {
const slugs = await getAllBlogSlugs();
const pathsWithParams = slugs.map((slug) => ({ params: { postSlug: slug } }));
return {
paths: pathsWithParams,
fallback: "blocking",
};
}
References:
NextJS.org Get Static Paths
export async function getServerSideProps({ query, locale }) {
const { id } = query;
if (!id) return { notFound: true };
return {
props: {
fallback: true,
query,
locale,
...(await serverSideTranslations(
locale,
["common", "header", "footer"],
nextI18nextConfig
)),
},
};
}

Categories

Resources