Dual nested dynamic routing in experimental app directory - javascript

I am using NextJS 13 and performing the following inside the app folder.
I am trying to use generateStaticParams function to achieve static generation pages on build.
This is the route: subpage/[categoryName]/[gifId]
So the route could be like following examples.
/subpage/fashion/1
/subpage/fashion/2
/subpage/fashion/3
/subpage/technology/1
/subpage/technology/2
/subpage/technology/3
/subpage/technology/4
... and so on.
The route subpage/[categoryName] won't have anything there. Might show an error or redirect some place.
The full path subpage/[categoryName]/[gifId] including the [gifId] is a must.
I need to perform REST requests to get the data for the pages.
How could I set this up inside my page.tsx file which will be located at: subpage/[categoryName]/[gifId]/page.tsx ?
If it was a single dynamic path, would be straight forward. See my implementation below for that.
But since is nested with 2 dynamic paths [categoryName] and [gifId] back to back, bit confused how to achieve this. Pls assist.
import MyComponent from "../../../components/MyComponent";
import { PartialGifProps, TagType} from "../../../utils/typings";
import axios from "axios";
import {apiDomain, defaultHeaders} from "../../../utils/constants";
const perPage = 40;
type Props = {
params: {
gifId: string,
},
}
export const generateStaticParams = async () => {
const url = `${apiDomain}/get_gif_count`; // I have access to modify the backend for this if it should contain category.
const fetchGifs = await axios.get(url, { headers: defaultHeaders });
const { total_count: totalCount } : TagType = fetchGifs.data;
const totalPages = Math.ceil(totalCount / perPage);
let paramsList = [];
for (let i = 1; i <= totalPages; i++) {
paramsList.push({ gifId: i.toString() })
}
// this paramsList would look like:
// [
// { gifId: '1', },
// { gifId: '2', },
// { gifId: '3', },
// .......
// ]
return paramsList;
}
const MyPage = async ({params: {gifId}}: Props) => {
const url = `${apiDomain}/get_partial?page=${gifId}&per_page=${perPage}`;
const fetchGifs = await axios.get(url, { headers: defaultHeaders });
const { gifs } : PartialGifProps = fetchGifs.data;
return (
<div className='text-white'>
<MyComponent gifs={gifs}/>
</div>
);
};
export default MyPage;

You can get categoryName in the same way you get gifId, through the params prop
type Props = {
params: {
gifId: string,
categoryName: string,
},
}
const MyPage = async ({params: {gifId, categoryName}}: Props) => {
console.log('categoryName =', categoryName);
const url = `${apiDomain}/get_partial?page=${gifId}&per_page=${perPage}`;
const fetchGifs = await axios.get(url, { headers: defaultHeaders });
const { gifs } : PartialGifProps = fetchGifs.data;
return (
<div className='text-white'>
<MyComponent gifs={gifs}/>
</div>
);
};

Related

next.js getStaticProps Serialize issue

I'm using next.js for a project where axios fetch in getStaticProps doesnot seem to work even though the URL is serialised in configuration.I tried serializing again by passing the response to JSON.parse but still cant find a solution.
import axios from "axios";
import Qs from "qs";
My axios config code below:
const axiosTmdbApi = axios.create({
baseURL: "https://api.themoviedb.org/3",
headers: { "content-Type": "application/json/" },
paramsSerializer: {
serialize: (params) =>
Qs.stringify({ ...params, api_key: apiKey }, { arrayFormat: "brackets" }),
},
});```
**My category which is passed as a parameter to invoke getTvList or getMovieList data below:**
import axiosTmdbApi from "./axiosTmdbApi";
export const category = {
movie: "movie",
tv: "tv",
};
export const type = {
top_rated: "top_rated",
popular: "popular",
};
const tmdbApi = {
getTvList: (tvType, params) => {
const url = "tv/" + type[tvType];
return axiosTmdbApi.get(url, params);
},
getMovielist: (movieType, params) => {
const url = "movie/" + type[movieType];
return axiosTmdbApi.get(url, params);
},
};
export default tmdbApi;```
Using getStaticProps to fetch my API
import tmdbApi from "../../api/tmdbApi";
import { type, category } from "../../api/tmdbApi";
const Movies = ({ data }) => {
console.log(data);
return (
<>
<h1 className="bg-success">Movies</h1>
</>
);
};
export default Movies;
export async function getStaticProps() {
let params = {};
let response;
response = await tmdbApi.getMovielist(type.popular, {
params,
});
const data = JSON.parse(JSON.stringify(response));
return {
props: { data },
};
}```
**Error :index.js?46cb:602 Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'ClientRequest'
| property 'socket' -> object with constructor 'TLSSocket'
--- property '_httpMessage' closes the circle **
Try adding console.log and see what values are being handled at each stage. Instead of const data = JSON.parse(JSON.stringify(response)), you should be doing const data = response.data.
and change return statement to
return {
props: { data: data || [] },
};

Getting stray fetch request to JSON files while running getStaticProps/ getStaticPaths

My page is working correctly, but When I look in the console I'm getting 5, 404 errors on fetch request. not sure where this is coming from..
I only get these 404s in production. Nothing in development.
Page File Structure
live site: https://real-fake-store.vercel.app/
github repo: https://github.com/Haviles04/real-fake-store
Here is the code I think it's coming from.
export async function getStaticPaths() {
const { products } = await import("../../../data/products/allData.json");
return {
paths: products.map((item) => {
const productId = item.id;
const productName = item.title
.toLowerCase()
.replace(/\s/g, "")
.toString();
const catName = item.category.name.toLowerCase().toString();
return {
params: {
catName,
productId: `${productId}=${productName}`,
},
};
}),
fallback:false,
};
}
export async function getStaticProps({ params }) {
const { categoryItems } = await import(
`../../../data/products/${params.catName}Data.json`
);
const pageProduct = categoryItems.find(
(item) => item.id === parseInt(params.productId)
);
return {
props: {
pageProduct,
},
};
}
and I'm getting this in the console
This was fixed by adding passHref to the LINK component that was directing to the page.

How to show content under a different URL to the file name using next.js

I have the following content structure:
src
|-- content
|-- terms-conditions.html
|-- makeup.html
The terms and conditions page has the following data which I can call
{ seo:
url: "makeup/terms-conditions"
}
I'd like the content of terms-conditions.html to actually show under the url makeup/terms-conditions. I've tried a few times using getStaticPaths to no avail so any guidance would be appreciated. Essentially all of my files will sit one level down from content but I'd like to actually serve them under the URL defined under seo/url
My getStaticPaths code in my [params].tsx file is as follows.
export async function getStaticPaths() {
const posts = getAllPosts(["slug"]);
return {
paths: posts.map((post) => {
return {
params: {
slug: post.slug,
},
};
}),
fallback: false,
};
}
getStaticProps:
export async function getStaticProps({ params }) {
const post = getPostBySlug(params.slug, ["slug", "data"]);
const content = (await post.content) || "";
return {
props: {
post: {
...post,
content,
},
},
};
}
And in my api file I have:
import fs from "fs";
import { join } from "path";
import matter from "gray-matter";
const postsDirectory = join(process.cwd(), "src/content");
export function getPostSlugs() {
return fs.readdirSync(postsDirectory);
}
export function getPostBySlug(slug: string, fields = []) {
const realSlug = slug.replace(/\.html$/, "");
const fullPath = join(postsDirectory, `${realSlug}.html`);
const fileContents = fs.readFileSync(fullPath, "utf8");
const { data, content } = matter(fileContents);
const items = { };
fields.forEach((field) => {
if (field === 'slug') {
items[field] = realSlug
}
if (field === 'data') {
items[field] = data
}
})
return items;
}
export function getAllPosts(fields = []) {
const slugs = getPostSlugs()
const posts = slugs
.map((slug) => getPostBySlug(slug, fields))
return posts
}

VueJS Component failing to render when fetching data

I'm new to Vue.JS and JavaScript, so I have awful times debugging these applications, specially with promises and asynchronous tools. I'm trying to build my first Vue component that fetches data from somewhere. I'm using the Google Sheets API and returning some cells of a sample sheet. My component looks like this:
<template>
<ul>
<li v-for="athlete in athletes" :key="athlete">
{{ athlete }}
</li>
</ul>
</template>
<script>
import readCopaPinheiros from '#/sheets/fetchData.js';
export default {
name: 'AthletesTable',
data () {
return {
loading: false,
athletes: null
}
},
created () {
this.fetchData()
},
methods: {
fetchData() {
this.loading = true;
readCopaPinheiros('inscritos').then(values => {
this.loading = false;
console.log(values)
this.athletes = values
});
},
}
}
</script>
<style>
</style>
EDIT 1
The fetchData script:
const fs = require('fs');
const { google } = require('googleapis');
const TOKEN_PATH = '';
const CREDENTIALS_PATH = ''
const credentials = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf-8'));
const {
client_secret: clientSecret,
client_id: clientId,
redirect_uris: redirectUris,
} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
clientId, clientSecret, redirectUris[0],
);
const token = fs.readFileSync(TOKEN_PATH, 'utf-8');
oAuth2Client.setCredentials(JSON.parse(token));
async function readSheet(spreadsheetId, range) {
const sheets = google.sheets({ version: 'v4', auth: oAuth2Client });
return sheets.spreadsheets.values.get({
spreadsheetId,
range,
})
.then(res => res.data.values)
.catch(err => console.log('Opa! Erro:', err));
}
function readSheetJsnofied(spreadsheetId, range) {
return readSheet(spreadsheetId, range)
.then(values => jsonifySheet(values));
}
function jsonifySheet(sheetValues) {
const header = sheetValues[0];
const values = sheetValues.slice(1);
return values.map((row) => {
const rowObj = ({});
for (let i=0; i < row.length; i++) rowObj[header[i]] = row[i];
return rowObj;
});
}
const readCopaPinheiros = d => readSheetJsnofied('sheetId', d);
export default readCopaPinheiros
For some reason the component doesn't render. I don't know what to do even to debug, all my console log tries never prints something to the console. Could someone help me understand what is going wrong?
EDIT 2
This error just shows up when trying to fetch data:
When I try to use a placeholder list with fake values directly in the data function it works. I don't believe that is a problem with the rendering itself, but how it interacts with the created and fetchData functions.
v-for="athlete in athletes"
This code only works when the athletes is an array. Initially, you set it as null so until the data from api is arrived, it will be null.
But the component still tries to render the component with your null athletes and will make the error.
You can try with this solution:
data () {
return {
loading: false,
athletes: []
}
},

stampit.js beginner needs some guidance

I'm implementing service between a view and a Rest API.
Beside, i'm completly new to stamp programming and i'm in search of some advices about this sort of code:
import {compose, methods} from '#stamp/it'
import ArgOverProp from '#stamp/arg-over-prop'
import {template} from 'lodash'
const createLogger = name => (...args) => console.log('['+ name + ']', ...args)
const HasHttpClient = ArgOverProp.argOverProp('httpClient')
const HasApiVersion = ArgOverProp.argOverProp('apiVersion')
const HasUrl = ArgOverProp.argOverProp('url')
const UseRestApi = compose(HasHttpClient, HasApiVersion, HasUrl).init([
function () {
this.getUrl = template(this.url)
this.useRestApiLog = createLogger('UseRestApi')
}
]).methods({
query: function query(method, {params, headers, body}) {
const {apiVersion} = this
const q = {
baseURL: this.getUrl({apiVersion}),
method,
...params != null && {params},
...headers != null && {headers},
...body != null && {body}
}
this.useRestApiLog('request config:', q)
return q
}
})
const WithGetOperation = compose(UseRestApi).init([
function () {
this.withGetOperationLog = createLogger('WithGetOperation')
}
]).methods({
'get': function get ({params}) {
const q = this.query('get', {headers: {'Accept': 'application/json'}, params})
this.withGetOperationLog('getting data')
return this.httpClient(q)
}
})
const CustomerRestApi = compose(WithGetOperation).init([
function () {
this.customerRestApiLog = createLogger('CustomerRestApi')
}
]).methods({
all: function all() {
this.customerRestApiLog('get all customers')
return this.get({params: {page: 1, limit: 15}})
}
})
const customerProvider = CustomerRestApi({
url: 'http://sample.com/<%=apiVersion%>/customers',
apiVersion: 'v1',
httpClient: function(config) {
return Promise.resolve({
status: 200,
config
})
}
})
const appLog = createLogger('APP')
customerProvider.all()
.then(r => appLog('HTTP response code:', r.status))
Am i in the right directions?
Especially, the createLogger thing seems ugly!
How to inject a prefixed logger into each stamp ?
How to extend that to warn, error, ... methods ?
Your logger looks just fine. 👍 It is not necessary to create every bit as a stamp. However, if you want to make the logger as a reusable stamp then you can do the same way as ArgOverProp is implemented.
Ruffly ArgOverProp is done this way:
const ArgOverProp = stampit.statics({
argOverProp(...args) {
return this.deepConf({ArgOverProp: [...args]});
}
})
.init(function (options, {stamp}) {
const {ArgOverProp} = stamp.compose.deepConfiguration;
for (let assignableArgName of ArgOverProp) {
this[assignableArgName] = options[assignableArgName];
}
});
Your logger could look like this (not necessary exactly like this):
import {argOverProp} from '#stamp/arg-over-prop';
const Logger = stampit(
argOverProp('prefix'),
{
methods: {
log(...args){ console.log(this.prefix, ...args); },
error(...args){ console.error(this.prefix, ...args); },
warn(...args){ console.warn(this.prefix, ...args); }
}
}
);
const HasLogger = stampit.statics({
hasLogger(name) {
return this.conf({HasLogger: {name}});
}
})
.init(_, {stamp}) {
const {HasLogger} = stamp.compose.configuration;
if (HasLogger) {
this.logger = Logger({prefix: HasLogger.name});
}
});
And usage:
const CustomerRestApi = stampit(
WithGetOperation,
HasLogger.hasLogger('CustomerRestApi'),
{
methods: {
all() {
this.logger.log('get all customers');
return this.get({params: {page: 1, limit: 15}});
}
}
);
I always prefer readability. So, the code above, I hope, is readable to you and any stampit newbie.
PS: a tip. The stampit and the stampit.compose you imported above are the same exact function. :) See source code.

Categories

Resources