Nextjs getStaticProps not fired - javascript

My [slug].js file, below, has two nextjs helper functions. The getStaticPaths and getStaticProps are exported. In my case, it creates the path posts/[slug]. There is one post file added called hello.json. Now when I navigate to localhost:3000/posts/hello it errors saying:
TypeError: Cannot read property 'fileRelativePath' of undefined. For line 10.
This makes sense after seeing that jsonFile is undefined. As a matter of fact, the whole getStaticProps is never called, the log in there is never logged. Why is this happening?
Thanks in advance.
import React from 'react';
import glob from 'glob';
import { usePlugin } from 'tinacms';
import { useJsonForm } from 'next-tinacms-json';
const Page = ({ jsonFile }) => {
console.log(121212, jsonFile);
// Create the tina form
const [post, form] = useJsonForm(jsonFile);
// Register it with the CMS
usePlugin(form);
return (
<h1>
{post.title}
</h1>
);
};
export default Page;
/**
* By exporting the async function called getStaticProps from a page, Next.js
* pre-renders this page at build time using the props returned by
* getStaticProps.
* The getStaticPaths function defines a list of paths that have
* to be rendered to HTML at build time.
*/
export async function getStaticProps({ ...ctx }) {
console.log(1212, ctx);
const { slug } = ctx.params;
const dynamicPath = `../../posts/${slug}.json`; // for eslint parsing error: "Cannot read property 'range' of null Occurred while linting"
const content = await import(dynamicPath);
console.log(121212, content);
return {
props: {
jsonFile: {
fileRelativePath: `/posts/${slug}.json`,
data: content.default,
},
},
};
}
export async function getStaticPaths() {
//get all .json files in the posts dir
const posts = glob.sync('posts/**/*.json');
const paths = posts.map(file => ({
params: {
slug: `${file.replace('.json', '')}`,
},
}));
return {
paths,
fallback: true,
};
};

After some more digging around I found the issue, posting here to hopefully help future readers with the same issue.
The culprit was this:
const dynamicPath = `../../posts/${slug}.json`; // for eslint parsing error: "Cannot read property 'range' of null Occurred while linting"
const content = await import(dynamicPath);
Using a variable in a dynamic import does not work, only strings or template literals. I used a variable because of a eslint parsing error that can only be resolved by downgrading to an earlier version of eslint. This causes eslint to not work for me in this file, but okay, at least the function is called.
This combined with the observation that the component code is called before the getStaticProps is called made the jsonFile variable undefined and the whole component erroring before it'll ever reach the getStaticProps. You can see that the log starting with 121212 is coming in earlier than 1212. Terminal logs:
121212 {
fileRelativePath: 'posts/hello.json',
data: { title: 'Not the actual data' }
}
1212 hello
This is counter intuitive to me as I figured it would first get the props and pass them to the component immediately, but sadly, defining default props is needed to work around this.
New code:
import React from 'react';
import glob from 'glob';
import { usePlugin } from 'tinacms';
import { useJsonForm } from 'next-tinacms-json';
const Page = ({ jsonFile }) => {
console.log(121212, jsonFile);
// Get content and form for Tina
const [content, form] = useJsonForm(jsonFile);
// Register it with the CMS
usePlugin(form);
return (
<h1>
{content.title}
</h1>
);
};
Page.defaultProps = {
jsonFile: {
fileRelativePath: 'posts/hello.json',
data: {
title: 'Not the actual data',
},
},
};
export default Page;
/**
* By exporting the async function called getStaticProps from a page, Next.js
* pre-renders this page at build time using the props returned by
* getStaticProps.
*/
export async function getStaticProps({ params: { slug } }) {
console.log(1212, slug);
// This line caused the issue
// const dynamicPath = (`../../posts/${slug}.json`; // for eslint parsing error: "Cannot read property 'range' of null Occurred while linting"
const content = await import(`../../posts/${slug}.json`);
return {
props: {
jsonFile: {
fileRelativePath: `posts/${slug}.json`,
data: content.default,
},
},
};
}
/**
* The getStaticPaths function defines a list of paths that have
* to be rendered to HTML at build time.
*/
export async function getStaticPaths() {
//get all .json files in the posts dir
const posts = glob.sync('posts/**/*.json');
return {
paths: posts.map(file => ({
params: {
slug: `${file.replace('.json', '')}`,
},
})),
fallback: true,
};
}

Related

react-electron ipcRenderer in useEffect does not rerender

I'm trying to make a global accessible object, so that its values can be changed and can be read from every component. I've create a classs with static fields:
export default class Settings {
static first: string;
static second: string;
}
Lets say I have two components in separate files:
import Settings from './Settings'
// located in firstComponent file
export default function FirstComponent() {
Settings.First = 'test' <---------------- SET VALUE
return (some html)
}
// located in secondComponent file
export default function SecondComponent() {
let qq = Settings.First <----------------------- ASSUME HERE IS VALUE "TEST"
}
But it is not working. How I can create static class/fields that will be accessible within all components like C# static classes. Is it even possible?
Thanks in advance
UPD
Looks like the problem in ipcRenderer:
export default function SettingsEditor() {
const [state, setState] = React.useState({
ipAddress: Settings.ipAddress,
});
useEffect(() => {
electron.ipcRenderer.once('readSettings', (args) => {
console.log('Filling settings');
console.log(args); <------ HERE WE HAVE VALUE like 10.10.10.10
setState({ ...state, ipAddress: args.ipAddress});
console.log(state.ipAddress); <------ UNDEFINED
state.ipAddress = args.ipAddress;
console.log(state.ipAddress); <------ UNDEFINED
Settings.ipAddress= args.ipAddress;
console.log(Settings.gateIpAddress); <------ UNDEFINED
});
electron.settingsApi.read();
}, []);
How I can handle this?
The reason is - I'm stupid =)
When I save settings I did it like this:
const settings = new Settings();
settings.ipAddress= state.ipAddress;
console.log(JSON.stringify(settings));
electron.settingsApi.save(settings); <------ PASS OBJECT
But when I return response it was:
ipcMain.on('readSettings', (event, _) => {
storage.getAll((err: Error, data: object) => {
if (err) {
return;
}
const { appSettings } = data;
const settings = new Settings();
settings.ipAddress= appSettings.ipAddress;
console.log('reading settings');
console.log(JSON.stringify(settings));
event.reply('readSettings', JSON.stringify(settings)); <-------- FACEPALM
});
});
What I can say - genius

Why can't I get query params in ```getStaticProps```?

I will make it simple:
Link: http://localhost:3000/product/bioCloths?activeCategory=medium&id=0
File path: pages/product/[product].js.
Expected: product: "bioCloths, activeCategory: "medium", id: "0"
using getStaticProps at [product].js:
const ProductDetails = (props) => {
console.log("initial props", props);
return <div>product details...</div>
};
export default ProductDetails;
export async function getStaticProps(context) {
return {
props: {
context: context
},
};
}
export async function getStaticPaths() {
return {
paths: [{ params: { product: "1" } }, { params: { product: "2" } }],
fallback: true,
};
}
Props returns: context: params: product: "bioCloths", excluding the query params.
Now if I use the deprecated getInitialProps instead:
ProductDetails.getInitialProps = (context) => {
const activeCategory = context.query.activeCategory;
const id = context.query.id;
const product = context.query.product;
return {
activeCategory: activeCategory,
id: id,
product: product,
};
};
props logs activeCategory: "medium" id: "0" product: "bioCloths"
I need to get these all of these props so I can fetch data before the client mounts.
I understand that getInitialProps is now deprecated, but it works. Why is not getStaticProps working, and should I use it instead of serverSideProps?
This is an ecommerce, and there is no way I can set getStaticPaths for hundreds of possibilities, to work along with getStaticProps so I guess that in this case I should use getInitialProps or getServerSideProps?
P.S - getServerSideProps hits me an error stating that it will only accept .json files.
According to one of the maintainers of Nextjs this is the reply for anyone learning this framework:
getStaticProps generates the page at build time. There's no possible way to know the custom query params your visitors can use at build time.
getInitialProps and getServerSideProps can know all the possible query params because the page is generated at runtime, whenever you receive a new request.
You can learn more about the above here.
"
This discussion can be read on Github
If your page uses a dynamic route, params contain the route parameters. For instance, If the page name is [id].js, then params will look as follows:
{ id: /* something */ }
You can get URL params from inside your getStaticProps or getServerSideProps function with the context argument.
Here’s an example with getStaticProps:
// pages/[id].js
export async function getStaticProps(context) {
const { params } = context;
const id = params.id;
const data = /* Fetching data with the id */
return {
props: data,
}
}
You can do the same with getServerSideProps:
// pages/[id].js
export async function getServerSideProps(context) {
const { params } = context;
const id = params.id;
/* ... */
}

Next js dynamic route in production return 403 error but should return 404. What could be the problem?

When I try to go to https://site.con/categories/ I receive 403 Forbidden, but when I go to https://site.con/categories/sport everything fine, and else routes work fine. What could be the problem?
pages/categories/[id].js:
import { categories } from '../../data/categories';
import CategoryLayout from '../../layouts/CategoryLayout';
const Page = ({ category }) => {
return <CategoryLayout data={category} />;
};
export async function getStaticPaths() {
return {
paths: Object.keys(categories).map(category => ({
params: {
page: category
}
})),
fallback: false
};
}
export async function getStaticProps({ params }) {
const category = categories[params.id] || [];
return {
props: { category }
};
}
export default Page;
faced the same problem. Look at your server - you probably don't have index.html inside the /categories folder.
I've found the solution here: How do I omit the html extension in next.js?
adding
module.exports = {
exportTrailingSlash: true,
}
would create physical folder for every route in /pages with index.html instead of the route.html
Hope it helps you!

Vue/Vuex - Module two depends on module one, and module one gets data from server

Check this out:
import accountModule from '#/store/modules/account/account';
import otherModule from '#/store/modules/other/other';
export default new Vuex.Store({
modules: {
account: accountModule,
other: otherModule,
}
});
The data initialization in other depends on the account module because the account module has user specific settings. Suppose other.state.list depends on account.state.settings.listOrder. However, I want the data for the account module to come from the server. Which is async. So when other is trying to get set up, it can't just try to reference account.state.settings.listOrder because the response from the server may not have come back yet.
I tried exporting a promise in accountModule that resolves with the module itself. But that approach doesn't seem to work.
import accountModulePromise from '#/store/modules/account/account';
accountModulePromise.then(function (accountMoudle) {
import otherModule from '#/store/modules/other/other';
...
});
This gives me an error saying that import statements need to be top level.
The following doesn't work either:
let accountModule = await import '#/store/modules/account/account';
import otherModule from '#/store/modules/other/other';
...
It gives me an error saying that await is a reserved word. I'm confused though, because https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import says that I should be able to do it.
Your last code block didn't work because of await have to be inside async function.
Remember, the await keyword is only valid inside async functions. If
you use it outside of an async function's body, you will get a
SyntaxError.
From MDN.
You can use Dynamic Module Registration:
accountModulePromise.then(async () => {
let otherModule = await import('#/store/modules/other/other');
store.registerModule('other', otherModule.default);
});
But when you want to get state or dispatch actions you have to check whether module is registered which is pretty bad.
In my opinion it would be better if you redesign your module structure to decoupling each other. Try to move your initialize code to main.js or App.vue then dispatch actions to update module states from that.
Updates
From your last update, Another idea to decoupling your store, I think you should store your list without order and sort it only when you use. You can do this with:
Computed property:
...
computed: {
list () {
let list = this.$store.state.other.list
let order = this.$store.state.account.settings.listOrder
if (!list || !order) return []
return someSort(list, order)
}
},
beforeCreate () {
this.$store.dispatch('other/fetchList')
this.$store.dispatch('account/fetchListOrder')
}
...
Or Vuex getters:
...
getters: {
list: (state) => (order) => {
return someSort(state.list, order)
}
}
...
...
computed: {
list () {
let order = this.$store.state.account.settings.listOrder
return this.$store.getters['others/list'](order)
}
}
...
Okay, so you have two modules. One with state that is fetched from the server, the other with state that is dependent on the first, correct?
I would suggest the following approach:
Set up your modules with empty 'state' to begin with. Then create an action within accountModule to set up the state from the server. Use a getter on other to order the list. Finally, dispatch your action upon app creation.
const account = {
namespaced: true,
state: {
listOrder: ''
},
mutations: {
setListOrder (state, newListOrder) {
state.listOrder = newListOrder
}
},
actions: {
async fetchServerState (ctx) {
let result = await fetch("/path/to/server")
ctx.commit('setListOrder', result.listOrder)
// or whatever your response is, this is an example
}
}
}
const other = {
namespaced: true,
state: {
unorderedList: []
},
getters: {
list (state, getters, rootState) {
return someSort(state.unorderedList, rootState.account.listOrder);
}
}
}
within App.vue (or wherever)
created () {
this.$store.dispatch('account/fetchServerState')
}

Passing a parameter to a function inside a named export object literal

When using a named export to return an object literal composed of functions, is it possible to pass a parameter to one of those functions?
For example, let's say the function below returns conditional results depending on if user's an admin:
// gridConfig.js
function getColumnDefs(isAdmin = false) {
// conditionally return columns
return {
orders: [ ... ],
...
}
}
export const config = {
columnDefs: getColumnDefs(),
rowDefs: getRowDefs(),
...
};
// main.js
import { config } from './gridConfig';
function doStuff() {
const { columnDefs, rowDefs } = config;
grid.columnDefs = columnDefs['orders'];
...
}
If I add the parameter to the function call inside the export, it says the param isn't defined. Adding a parameter to the export alias gives syntax errors. Even if it allowed this, I'm not clear where I'd pass my param inside main.js.
Is there some way of passing a parameter when structuring an export in this manner?
Maybe keeping it simple can be useful :)
export const config = (isAdmin) => ({
columnDefs: getColumnDefs(isAdmin),
rowDefs: getRowDefs(),
...
});
// Import
import { config } from '[...]'; // Placeholder path of import
const myConfigFalse = config(false);
const myConfigTrue = config(true);
export const config = admin => ({
columnDefs: getColumnDefs(admin),
rowDefs: getRowDefs(),
});
// main.js
import { config } from './gridConfig';
function doStuff() {
const { columnDefs, rowDefs } = config(admin);//get the admin variable set before this line
grid.columnDefs = columnDefs['orders'];
...
}

Categories