I'm new in nextjs and I need to do a dynamic URL using one API parameter
I have some like this data for the API
[
{
"categories" : {
"department": "HR"
"location": "xxxx"
},
"id": "4d2f341-k4kj6h7g-juh5v3",
},
{
"categories" : {
"department": "Operarions"
"location": "xxxx"
},
"id": "4qd3452-fsd34-jfd3453",
},
{
"categories" : {
"department": "Marketing"
"location": "xxxx"
},
"id": "4d2f341-k4kwer37g-juh5v3",
},
]
And I need to do something like that in my index.js
return(
<h1>More details</h1>
<a href="https://page.com/${id}"> link <a>
)
If someone can help me pls
Yes, will try to help you
Imagine you have a folder with the name records, so your url will be /records
In data you provided, you have several categories with ids which ones you like to have as links.(generated records dynamically)
According to nextjs docs, you can create getStaticProps page and generate static pages (it means it's very good for SEO, so use it on visible by SEO pages)
Imagine you have the next structure:
/records
[pid].tsx (or js)
index.tsx
This structure means that if you go to /records URL your index file will be triggered. And for /records/4d2f341-k4kj6h7g-juh5v3 your [pid] file will be triggered.
in index.tsx
import Link from 'next/link'
const Records= ({ records }) => {
return(
<div className="container">
<h1>Records</h1>
{records.map(record=> (
<div>
<Link href={'/records/' + record.id} key={record.id}>
<a>
{record.categories.department}
</a>
</Link>
</div>
))}
</div>
);
}
export const getServerSideProps = async () => {
//your API path here
const res = await fetch('replace_by_your_api_route');
const data = await res.json()
return {
props: {records: data}
}
}
export default Records
in your [pid].tsx
import GeneralUtils from "lib/generalUtils";
const RecordDetails= ({record}) => {
return(
<div>
{record?
<div >
{record.categories.department} {record.categories.location}
</div>
: ''}
</div>
)
}
export async function getStaticProps(context) {
const pid = context.params.pid;
//your api path to get each record where pid it is your parameter you are passssing to your api.
const res = await fetch(process.env.APIpath + '/api/public/getRecord?pid=' + (pid));
const data = await res.json();
return {
props: {record: data}
}
}
export async function getStaticPaths() {
const res = await fetch('your_api_path_same_as_in_index_tsx')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
//your id of each record, for example 4d2f341-k4kj6h7g-juh5v3
params: { pid: post.id.toString() },
}))
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: 'blocking' }
}
export default ProductDetails;
Output: - user types
/records - he will have list of recods with links
/records/4d2f341-k4kj6h7g-juh5v3 - he will have page with info of d2f341-k4kj6h7g-juh5v3
Check this info very carefully and this video
Related
I need to update data in an array that has multiple objects where a user will input a new balance that will update the old balance state. The array consists of a company name with an array called details, and that array holds objects containing employee information(name, balance, notes), for this question I am just using notes to simplify things. I am using Axios PUT to access the id of the nested object, I get the id from a Link that is passed via useParams hook.
My issue is in the Axios PUT request. Before I had a schema that was just a data object (no arrays were in it) and the PUT req was working fine. Then I needed to change the schema to an array with multiple objects and now I cannot seem to update the data. I am able to target the data through the console log but when I take that code from the console and apply it, the state still doesn't change. Even in Postman, the only way for me to successfully update is to get the Shema from a GET request and paste that schema in the PUT request and change some data in it, then I hit send and it updates, but to get it to update again I need to hit send twice (this shouldn't be, no? ).
I am able to access the data and render it in other components by mapping it twice as shown below:
setBalance(res.data.data.details.map((r) => r.balance));
My question: How can I edit the below code to update the state correctly?
setNotes([...details, res.data.data.details.map((r) => r.notes )]);
However, I am really struggling with how to do this in the Axios PUT request.
Here is my code:
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { useParams } from "react-router-dom";
import axios from "axios";
const AddForm = () => {
const [newBalance, setNewBalance] = useState("");
const [details, setDetails] = useState([]);
const [notes, setNotes] = useState("");
const [total, setTotal] = useState("");
const { id } = useParams();
const history = useHistory();
//update balance
const updateBal = () => {
// function to calculate balance
};
const updateBalHandler = (e) => {
e.preventDefault();
axios({
method: "PUT",
url: `http://localhost:5000/update-snapshot-test/${id}`,
data: {
balance: total
notes: notes
},
}).then((res) => {
history.push(`/success/` + id);
setNotes([...details, res.data.data.details.map((r) => r.notes )]); //this code isolates the notes state but does not update it
});
};
return (
<form
action="/update-snapshot/:id"
method="post"
onSubmit={updateBalHandler}
>
<Input
setInputValue={setNewBalance}
inputValue={newBalance}
inputType={"number"}
/>
<Input
setInputValue={setTotal}
inputValue={total}
inputType={"number"}
/>
<TextArea
setInputValue={setNotes}
inputValue={notes}
inputType={"text"}
/>
<Button onClick={() => { updateBal(); }} >
Update
</Button>
<Button type="submit">
Save
</Button>
</form>
);
};
export default AddForm;
Here is my data structure from Mongo DB
{
"message": "Post found",
"data": {
"company": "Riteaid",
"_id": "1",
"details": [
{
"employee": "jane doe",
"balance": "3",
"notes": "some notes",
"_id": "2"
},
{
"employee": "john doe",
"balance": "1",
"notes": "some more notes",
"_id": "3"
}
],
}
}
You have the id, so you have to search for the relevant object, update it and pass it to the setNotes() setter.
let localNotes = res.data.data.details.map((responseDetail) => {
if (detail._id === id){
let newNotes = [...responseDetail.notes, ...details];
return {
...responseDetail,
notes: newNotes
};
}
return responseDetail;
});
if (localNotes.length){
setNotes(localNotes);
}
Does this solve your problem?
The answer was in the backend, the front end was fine, the code did not need any of the additions, it should just be:
const addBalHandler = (e) => {
e.preventDefault();
axios({
method: "PUT",
url: `http://localhost:5000/endpoint${id}`,
data: {
balance: total,
notes: notes,
date: date,
},
}).then((res) => {
history.push(`/success/` + id);
console.log(res.data);
});
};
Next.js enables us to define dynamic routes in our apps using the brackets [param]. It allows us to design URL in such a way that for example language is passed as parameter. When no route matches the user is redirected to error page.
The idea is simple and documentation abut dynamic routes in Next.js is rather limited. Does anybody know if it's possible to assign a default value to dynamic route parameter?
There are docs pages about i18n routing and redirects and special support for locale parameters (which I have not used personally).
In a more general sense, it sounds like what you want is optional catch all routes.
You can define a file in your foo directory with the name [[...slug]].js. The params which correspond to a path like /foo/us/en is { slug: ["us", "en"] } where each segment of the path becomes an element of the slug array.
You can use getStaticPaths to generate all of the known country/language pairs. Setting fallback: true allows for the user to enter another combo and not get a 404.
export const getStaticPaths = async () => {
return {
paths: [
{ params: { slug: ["us", "en"] } },
{ params: { slug: ["us", "es"] } },
{ params: { slug: ["ca", "en"] } },
{ params: { slug: ["ca", "fr"] } },
/*...*/
],
fallback: true, // allows unknown
};
};
As far as redirection, it depends on whether you want an actual redirect such that typing in /foo leads to /foo/us/en or if those are two separate pages which show the same content. I'm going to assume that we want an actual redirect.
You'll convert from slug to props in your getStaticProps function. This is also where you implement your redirects. I'm going to assume that you have (or can create) some utility functions like isValidCountry(country) and getDefaultLanguage(country)
export const getStaticProps = async ( context ) => {
const [country, language] = context.params?.slug ?? [];
// if there is no country, go to us/en
if (!country || !isValidCountry(country)) {
return {
redirect: {
statusCode: 301, // permanent redirect
destination: "/foo/us/en",
},
};
}
// if there is no language, go to the default for that country
if (!language || !isValidLanguage(language, country)) {
return {
redirect: {
statusCode: 301, // permanent redirect
destination: `/foo/${country}/${getDefaultLanguage(country)}`,
},
};
}
// typical case, return country and language as props
return {
props: {
country,
language,
},
};
};
There are things that you can do in the component itself with useRouter and isFallback, but I'm not sure if it's needed. In dev mode at least I'm getting proper redirects.
/foo/ca/en - ok
/foo/ca/fr - ok
/foo/ca/xx - redirects to /foo/ca/en
/foo/ca - redirects to /foo/ca/en
/foo - redirects to /foo/us/en
Complete code with TypeScript types:
import { GetStaticPaths, GetStaticProps } from "next";
export interface Props {
country: string;
language: string;
}
export default function Page({ country, language }: Props) {
return (
<div>
<h1>
{country} - {language}
</h1>
</div>
);
}
const pairs = [
["us", "en"],
["us", "es"],
["ca", "en"],
["ca", "fr"],
];
const isValidCountry = (c: string) => pairs.some(([cc]) => cc === c);
const isValidLanguage = (l: string, c: string) =>
pairs.some(([cc, ll]) => cc === c && ll === l);
const getDefaultLanguage = (c: string) =>
pairs.find(([cc]) => cc === c)?.[1] ?? "en";
export const getStaticProps: GetStaticProps<Props, { slug: string[] }> = async (
context
) => {
const [country, language] = context.params?.slug ?? [];
// if there is no country, go to us/en
if (!country || !isValidCountry(country)) {
return {
redirect: {
statusCode: 301, // permanent redirect
destination: "/foo/us/en",
},
};
}
// if there is no language, go to the default for that country
if (!language || !isValidLanguage(language, country)) {
return {
redirect: {
statusCode: 301, // permanent redirect
destination: `/foo/${country}/${getDefaultLanguage(country)}`,
},
};
}
// typical case, return country and language as props
return {
props: {
country,
language,
},
};
};
export const getStaticPaths: GetStaticPaths<{ slug: string[] }> = async () => {
return {
paths: pairs.map((slug) => ({
params: { slug },
})),
fallback: true, // allows unknown
};
};
I'm trying to get image url for nested JSON object.
Tried {post.image.url} but I get an error saying url undefined
I appreciate any help or guidance that could be offered. New to Javascript / React but after an hour of Googling and searching couldn't come up with a solution for this. Must be missing something simple :-P
Here's my code...
export const getAllPosts = async () => {
return await fetch(
`https://notion-api.splitbee.io/v1/table/${NOTION_BLOG_ID}`
).then((res) => res.json());
}
export async function getStaticProps() {
const posts = await getAllPosts()
return {
props: {
posts
},
};
}
function Blog({ posts }) {
return (
<div>
{posts.map((post) => (
<Link href="/blog/[slug]" as={`/blog/${post.slug}`}>
<div>
<div className='text-6xl'>{post.title}</div>
<img className='w-24' src={post.imgUrl}></img>
{/*{post.image.rawUrl}*/}
</div>
</Link>
))}
</div>
);
}
export default Blog
Here's the JSON...
[
{
"id": "90ee0723-aeb5-4d64-a970-332aa8f819f6",
"slug": "first-post",
"date": "2020-04-21",
"Related to Notion API Worker (Column)": [
"0976bfa6-392a-40b0-8415-94a006dba8d9"
],
"imgUrl": "https://www.notion.so/image/https:%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F689883de-2434-4be3-8179-a8ba62a7bc1e%2Fsnowmountain.jpg?table=block&id=90ee0723-aeb5-4d64-a970-332aa8f819f6&cache=v2",
"image": [
{
"name": "snowmountain.jpg",
"url": "https://www.notion.so/image/https:%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F689883de-2434-4be3-8179-a8ba62a7bc1e%2Fsnowmountain.jpg?table=block&id=90ee0723-aeb5-4d64-a970-332aa8f819f6&cache=v2",
"rawUrl": "https://s3-us-west-2.amazonaws.com/secure.notion-static.com/689883de-2434-4be3-8179-a8ba62a7bc1e/snowmountain.jpg"
}
],
"title": "My first blogpost bruce"
}
]
The image property of a post is an array as denoted by the bracket on the end of line 1:
1. "image": [
2. {
3. "name": "snowmountain.jpg",
4. "url": "https://www.notion.so/image/https:%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F689883de-2434-4be3-8179-a8ba62a7bc1e%2Fsnowmountain.jpg?table=block&id=90ee0723-aeb5-4d64-a970-332aa8f819f6&cache=v2",
5. "rawUrl": "https://s3-us-west-2.amazonaws.com/secure.notion-static.com/689883de-2434-4be3-8179-a8ba62a7bc1e/snowmountain.jpg"
6. }
7. ],
If you know that there will always be exactly one image you could access it directly by referencing the first array item.
function Blog({ posts }) {
return (
<div>
{posts.map((post) => (
<Link href="/blog/[slug]" as={`/blog/${post.slug}`}>
<div>
<div className='text-6xl'>{post.title}</div>
<img className='w-24' src={post.imgUrl}></img>
{post.image && post.image[0].rawUrl}
</div>
</Link>
))}
</div>
);
}
Note that this could err if the array is undefined / null or if it's empty, so you should probably safe-guard it by only displaying something if it exists.
There is no need to call then() method, if you use async\await.
As mdn says:
await can be put in front of any async promise-based function to pause
your code on that line until the promise fulfills, then return the
resulting value.
export const getAllPosts = async () => {
return await fetch(
`https://notion-api.splitbee.io/v1/table/${NOTION_BLOG_ID}`
);
}
export async function getStaticProps() {
const posts = await getAllPosts()
return {
props: {
posts
},
};
}
Getting an error when building site even though Gatsby develop works fine, the markdown is coming from Netlify CMS. The specific error is:
error Building static HTML failed for path "/null"
See our docs page on debugging HTML builds for help https://gatsby.app/debug-html
11 | export default function Template({ data }) {
12 | const { markdownRemark } = data;
> 13 | const { frontmatter, html } = markdownRemark;
| ^
14 | return (
15 | <Layout>
16 | <SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
WebpackError: TypeError: Cannot read property 'frontmatter' of null
- blogTemplate.js:13 Template
lib/src/templates/blogTemplate.js:13:11
I have been reading the other similar paths and have tried changing the path in my markdown files from the cms but it was to no avail.
gatsby-config.js
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/blog`,
name: "markdown-pages"
}
},
blogTemplate.js
export default function Template({ data }) {
const { markdownRemark } = data;
const { frontmatter, html } = markdownRemark;
return (
<Layout>
<SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
<div className="blog-posts">
<h1 className="blog-title">{frontmatter.title}</h1>
<h2 className="blog-date">{frontmatter.date}</h2>
<div
className="blog-post-content"
dangerouslySetInnerHTML={{ __html: html }}
/>
<Link to="/blog/" className="backBTN">
Go Back
</Link>
</div>
<Footer />
</Layout>
);
}
export const pageQuery = graphql`
query($path: String!) {
markdownRemark(frontmatter: { path: { eq: $path } }) {
html
frontmatter {
date(formatString: "MMMM DD, YYYY")
path
title
}
}
}
`;
gatsby-node.js
const path = require("path");
exports.createPages = ({ actions, graphql }) => {
const { createPage } = actions;
const blogPostTemplate = path.resolve(`src/templates/blogTemplate.js`);
return graphql(`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 1000
) {
edges {
node {
frontmatter {
path
}
}
}
}
}
`).then(result => {
if (result.errors) {
return Promise.reject(result.errors);
}
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: `${node.frontmatter.path}`,
component: blogPostTemplate,
context: {}
});
});
});
};
GraphQL
the query returns the following error unless I pass in a specific path of one of the blog posts then it returns the correct results
{
"errors": [
{
"message": "Variable \"$path\" of required type \"String!\" was not provided.",
"locations": [
{
"line": 1,
"column": 7
}
]
}
]
}
graphql screenshot with variable passed in
I think that is all the relevant code that is needed here, just let me know if anything else is needed.
Check out this very first line of the error:
error Building static HTML failed for path "/null"
I bet one of your .md has an empty path. Maybe you can try filtering to find a markdown node with empty frontmatter path, or null?
If that path was coming directly from netlifyCMS & you use the string widget, IIRC you can pass in a default options so it'll fallback to that
All the Gatsby starter demos have a path like /gatsby-starter-blog/hi-folks/
How do I set it up with /2015-05-28/hi-folks/ or just the year with /2015/hi-folks/.
Thanks!
Two options:
1) Just put the blog posts in directories named like you want the url to be so in this case /2015-05-28/hi-folks/index.md.
2) You can programmatically set paths by exporting a function from gatsby-node.js called rewritePath. It is called for each page with filesystem data for the file the page comes from + the page's metadata. So say you want to set the date of the post in your markdown's frontmatter and have each post be a simple markdown file with paths like /a-great-blog-post.md
So to do what you want, add to your gatsby-node.js something like:
import moment from 'moment'
exports.rewritePath = (parsedFilePath, metadata) => {
if (parsedFilePath.ext === "md") {
return `/${moment(metadata.createdAt).format('YYYY')}/${parsedFilePath.name}/`
}
}
rewritePath is no longer supported in Gatsby. Here's a solution confirmed to works in Gatsby 2.3,
const m = moment(node.frontmatter.date)
const value = `${m.format('YYYY')}/${m.format('MM')}/${slug}`
createNodeField({ node, name: 'slug', value })
In my gatsby-node.js file I added a slug generator and a template resolver.
Inside src/posts I added a folder 2020 inside that folder I create folders that make slug address paths like so my-blog-post and inside those folders, I have an index.md file.
Now I have URL's that look like http://www.example.com/2020/my-blog-post/.
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
const { createFilePath } = require(`gatsby-source-filesystem`);
// Generates pages for the blog posts
exports.createPages = async function ({ actions, graphql }) {
const { data } = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`);
data.allMarkdownRemark.edges.forEach((edge) => {
const slug = edge.node.fields.slug;
actions.createPage({
path: slug,
component: require.resolve(`./src/templates/blog-post.js`),
context: { slug: slug },
});
});
};
// Adds url slugs to the graph data response
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode, basePath: `posts` });
createNodeField({
node,
name: `slug`,
value: slug,
});
}
};
Inside my src/templates/blog-post.js file I have:
import React from 'react';
import { graphql } from 'gatsby';
import Layout from '../components/layout';
export default function BlogPost({ data }) {
const post = data.markdownRemark;
const tags = (post.frontmatter.tags || []).map((tag, i) => {
return <li key={i}>#{tag}</li>;
});
return (
<Layout>
<article className="post">
<header>
<h1>{post.frontmatter.title}</h1>
<div className="meta">
<time>{post.frontmatter.date}</time>
<ul>{tags}</ul>
</div>
</header>
<section dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
</Layout>
);
}
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
date(formatString: "DD-MMMM-YYYY")
tags
}
}
}
`;
My markdown files then use frontmatter data like so:
---
title: Post title here
date: "2020-05-25T14:23:23Z"
description: "A small intro here"
tags: ["tag1", "tag2"]
---
Main post content would be here...