How to create routes with dynamic params in gatsbyjs [duplicate] - javascript

I have setup gatsby project using this link. It is working correctly.
Now I know how to create route by defining the component inside the pages folder. But now I have a new challenge I need to create one dynamic route so that I can pass my id in it (Just like reactjs).
<Route path: "/path/:id"/>
How do I do that in gatsby?

You have to explicitly tell gatsby that a path should be dynamic. From the docs:
// gatsby-node.js
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/app/)) {
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}
and then you can use dynamic routing in src/pages/app.js
import { Router } from "#reach/router"
const SomeSubPage = props => {
return <div>Hi from SubPage with id: {props.id}</div>
}
const App = () => (
<Layout>
<Link to="/app/1">First item</Link>{" "}
<Link to="/app/2">Second item</Link>{" "}
<Router>
// ...dynamic routes here
<SomeSubPage path="/app/:id" />
</Router>
</Layout>
)
export default App
Everything that goes to /app/* will be handled dynamically now. You should find your id as usual in the props.
Have a look at their authentication example https://github.com/gatsbyjs/gatsby/tree/master/examples/simple-auth

You can use square brackets ([ ]) in the file path to mark any dynamic segments of the URL. For example, in order to edit a user, you might want a route like /user/:id to fetch the data for whatever id is passed into the URL.
src/pages/users/[id].js will generate a route like /users/:id
src/pages/users/[id]/group/[groupId].js will generate a route like /users/:id/group/:groupId
Reference: https://www.gatsbyjs.com/docs/reference/routing/file-system-route-api#creating-client-only-routes

You can use gatsby-plugin-create-client-paths. It uses matchPath. For more info check
https://www.gatsbyjs.org/docs/gatsby-internals-terminology/#matchpath
https://www.gatsbyjs.org/packages/gatsby-plugin-create-client-paths/

This answer is Super late, but for anyone in the future who is faced with this problem, I have a simpler solution.
In Gatsby terms it's called a Splat Route.
For examples, If you want some page "domain.com/profile/[id]", where id can be any number, which will be used to display different data inside the website, you should name your page as [...id].
Now inside the page you can access this id as
const ProfilePage = (props) => <div>This page is for id number {props.params.id}</div>
Note: Don't miss the 3 dots, that is what signifies a splat route in gatsby.

Related

Does Nextjs generate duplicate pages at build time?

So I haven't found this answer anywhere and I would like to know this so that's why posting here. Suppose I have a blog for which I want to use SSG and ISR. SSG for the homepage and ISR for the individual posts.
So if I generated the homepage at build time with SSG to display 10 posts with the following code:
index.js file:
export const getStaticProps = async () => {
const posts = await get10PostsFromDB();
return {
props: {
posts
}
}
}
And then, if I use this code for each post and also use getStaticPaths to generate those exact 10 posts:
[post].js
export const getStaticPaths = async () => {
const posts = await get10PostsFromDB();
const paths = posts.map( ( item ) => ( {
params: {
slug: item.slug
}
} ) );
return {
paths,
fallback: 'blocking'
};
}
export const getStaticProps = async ({ params }) => {
const post = await getASinglePostFromDB( params.slug );
return {
props: {
post
},
revalidate: 1,
notFound: true
}
}
Will there be duplicate SSG generated pages as 10 are generated at build time for the homepage and then again those 10 pages are generated for getStaticPaths as well? Or what I'm thinking is totally wrong?
I think you have a bit of a misunderstanding of what getStaticPaths and getStaticProps do.
Short answer: no, it will not duplicate the pages.
Let me explain why:
getStaticProps vs getStaticPaths
getStaticProps (Static Generation): Fetch data at build time.
getStaticPaths (Static Generation): Specify dynamic routes to pre-render pages based on data.
What does getStaticProps do?
NextJS has this built-in page pre-rendering but this built-in process has a flaw if you wanna call it like this.
Take this code for example:
const HomePage = () => {
const [loadedPosts, setLoadedPosts] = useState();
useEffect(() => {
setLoadedPosts(DUMMY_POSTS);
}, []);
return (
loadedPosts ? (
<PostList posts={loadedPosts} />
) : ''
)
}
Note: This is a simple demo to convey the concept. In normal cases this issue will occur when you fetch data from an API in the useEffect hook.
Explanation: Let's imagine we are rendering a list of posts. DUMMY_POSTS is our data that comes from the db for example.
What happens here is that the useEffect hook will wait for the component to render before updating out loadedPosts.
When NextJS pre-renders a page it takes the snapshot of the first component rendering cycle as its content and that might be missing crucial data. This means that NextJS does not update the snapshot of our code AFTER the new data has come. This as you know is bad for SEO.
After this HTML page was received on the client, React will actually take over, the page is hydrated as this process is called, which means that now React will turn this into a single page application, take over control, and then this useEffect function will be executed, data might be fetched and the page might be updated - not on the server, not on the pre-rendered page but instead after this page was received in the browser. I.e no SSR.
Here's where getStaticProps() comes to play.
const HomePage = (props) => {
return (
<PostList posts={props.posts} />
)
}
export function getStaticProps() {
// fetch data from an API
return {
props: {
posts: DUMMY_POSTS
},
revalidate: 10
};
};
export default HomePage;
getStaticProps runs at build time and it fetches the data DUMMY_POSTS and then assigns it to props. This is then passed as props of the HomePage component where we can access props.posts.
What is getStaticPaths?
getStaticPaths is a function that you need to export in a page component file that is:
A dynamic page and
Is using getStaticProps
You DO NOT need it in a file that is using getServerProps and DO NOT NEED IT WITHOUT getStaticProps
Here I will use a very simple example to explain, but normally you generate the paths dynamically as you have done in your example:
export async function getStaticPaths() {
return {
fallback: false,
paths: [
{
params: {
postdId: 'p1'
}
},
{
params: {
postId: 'p2'
}
},
]
};
};
In the above example, we have two posts with IDs p1 and p2.
With getStaticProps a page is generated during the build process. This means that Next.js needs to pre-generate all versions for all dynamic pages in advance for all supported dynamic paths. Since it's dynamic Next.js needs to know for which IDs it needs to pre-generate a page. These functions are run during the build process NOT when a client visits the page. I.e. without getStaticPaths these dynamic pages will not be pre-generated on the server which kills the purpose of Next.js.
getStaticPaths tells Next.js what are the exact dynamic paths (in our case which post IDs) it needs to pre-generate. If the user enters an ID for which we did not pre-generate a page they will see a 404 error.
getStaticPaths returns an object where we describe all of the dynamic segment values. It has a paths property, which is an array that takes an object for each version of the dynamic page. This object has a params property, which is also an object that holds all of the key-value pairs that lead to your dynamic page.
TLDR;
getStaticProps fetches the data at build time before the component has loaded so that Next.js can pre-render the page WITH the data that we are fetching.
getStaticPaths tells Next.js about dynamic pages that need to be pre-rendered, because Next.js will not pre-render them on its own as it does not know exactly the dynamic IDs (routes). With this function you are basically telling Next.js how your dynamic routes are called so it knows what to pre-render. Otherwise it will show a 404 page.
Conclussion:
NextJS does not generate duplicate pages at build time.
EDIT:
In regards to pre-generating pages with getStaticPaths...
You DO NOT need to pre-generate every single dynamic route. In order to be able to load the other routes without a 404 error, you can add fallback: true or fallback: 'blocking'
These options can help in cases where you have a lot of pages that need to be pre-generated. In the case we have a website like Amazon with millions of products, pre-rendering all of them might take super long and there may be products that are rarely visited. Therefore, pre-generating such products is a waste of resources.
In such cases, we can set fallback: true and pre-generate some pages, not all.
export async function getStaticPaths() {
return {
fallback: true,
paths: [
{
params: {
prodId: 'p1'
}
}
]
}
}
Even if we don't list all of the pages, this setting tells NextJS that other pages can be valid and should be loaded when they are visited. They are generated just in time. This allows to pre-generate highly visited pages and pre-generate other pages only when they are needed.
However, if the request with the unsupported ID is sent directly (not internally through clicking on a product), it will return a 404. In such cases, we need to have a fallback case in our component.
const ProductDetailPage = ({ loadedProducts }) => {
if (!loadedProducts) {
return <p>Loading...</p>
}
return (
<>
<h1>{loadedProducts.title}</h1>
<p>{loadedProducts.description}</p>)
</>
)
}
In the above code, the user will see the Loading indicator instead of a 404 page, until the data comes and is then displayed on the page.
An alternative would be to set fallback: 'blocking'. In those cases, we don't need a fallback in the component, but the response will take a little longer.
const ProductDetailPage = ({ loadedProducts }) => {
return (
<>
<h1>{loadedProducts.title}</h1>
<p>{loadedProducts.description}</p>)
</>
)
}
export async function getStaticPaths() {
return {
fallback: 'blocking,
paths: [
{
params: {
prodId: 'p1'
}
}
]
}
}
It depends on your case. Sometimes you need to show something quickly (fallback: true). Sometimes it's worth waiting for it and you don't want to show an incomplete page to your visitors (fallback: 'blocking').
Read more about NextJS's Data fetching here.

Get Target Path in Gatsby [duplicate]

This question already has answers here:
How to get previous url in react gatsby
(4 answers)
Closed 2 years ago.
Is there any way to find the path of the page that a user is on on a Gatsby site and store it in a const so that it can be checked next to the current path the user is on? In other words if they're on the /about page, and are clicking a link to navigate to the /contact page, I need to get the paths of both so that I can check them against each other. I know I can get location.pathname for the current url, but how do I find the path that they are navigating to when the click a link?
Since your code structure isn't clear. Assuming a simple anchor tag, you can do something like this:-
<a href="/new/link" onClick={getHref}>New Link</a>
And in your getHref method
function getHref(event){
console.log(event.target.href); // should log '/new/link'
}
Check if this works in your case.
Please forgive for any typo, I havent validated it.
Gatsby exposes props (because it extends from #reach/router from React) by default on the top-level components (this means pages). You can pass it to the child components as you wish or store it in a const or in a React's state.
Without knowing any page structure, I'll provide something dummy as an example:
import React from "react"
import { graphql } from "gatsby"
const YourPage = ({ location, data }) => {
console.log('your page is ', location.pathname)
return <div>Dummy content</div>
}
export default Page
export const query = graphql`
query PageQuery {
site {
siteMetadata {
siteURL
}
}
}
`
Your information is stored under props.location, that's why you can destructure it in the component's declaration.
In the case above, I've used pathname property but you have a bunch exposed. Check it out to find out which one fits your requirements.

Next.js: one page that match root '/' and dynamic route '/param'

I have a website as a single page that is using Next.js. I have the home page on route / that show a list of products. The code of this page is located in pages/index.js. Each product has an id so I can jump to it using /#product-id.
To make it more url friendly I replicate this behaviour using the product-id as a second param in the route like that: /product-id.
What I do is simply looking on the product-id param using useRouter():
const selectedProductId = useRouter().query['product-id']
And then scroll to the element with this id using js:
document.getElementById(selectedProductId ).scrollIntoView()
So I to change my script name from /pages/index.js to /pages/[product-id].js.
So now the route /1234 work has expected but if I go to / I get error 404.
So has someone an idea how I can match / and /param using one js file?
Optional catch all routes
Catch all routes can be made optional by including the parameter in double brackets ([[...slug]]).
Nextjs has file system based routing, so if you remove /pages/index.js of course you will get an 404 error. Also /pages/index.js and /pages/[product-id].js will render two separate pages.
To answer your question, if it is possible to match two routes like / and /[productId] in one file using nextjs I don't think that is possible however similar results can be achieved by using shallow routing specific to your use case.
So for your use case, I suggest using shallow routing unless you want to render the same component in both pages just to get the product-id or want to make use of hash URLs.
You can make product-id a query string parameter and update it using shallow-routing. Here is an example,
Keep /pages/index.js
import { useRouter } from 'next/router';
const router = useRouter()
// when want to change the productId call
router.push('/?productId=1234', undefined, { shallow: true })
// call the scrollToView inside a useEffect hook
useEffect(() => {
const productId = router.query.productId
// get the element using the productId above then call scrollIntoView()
})
// if using useEffect with the dependency router.query.productId,
// when you change the productId once and scroll up and try to change to the same -
// productId again it will not scroll to view, hence not using the dependency array
// at all
To explain more on what shallow routing does
Shallow routing will allow the change of URL without running the data fetching methods i.e getStaticProps or getServerSideProps again. Which will make the updated query and pathname available without changing the state. Read more about it nextjs docs.
Option 1: Extract the shared code
You could extract a Page component to a separate file and then import it in both /pages/index.js and /pages/[product-id].js, so the code is not duplicated.
Option 2: Use experimental rewrites feature
Assuming you have /pages/[product-id].js you can show this page when a user requests /.
You would need to add next.config.js.
module.exports = {
experimental: {
async rewrites() {
return [
{ source: "/", destination: "/[product-id]" },
];
}
}
}
So, when a user requests / they would see the content of /[product-id], just with the empty product id.
Note, that at the moment rewrite doesn't support auto-rendered dynamic pages, so you have to disable auto-rendering for the dynamic page.
You can do that by adding getServerSideProps to /pages/[product-id].js.
export async function getServerSideProps() {
return {
props: {},
}
}

Does React Router necessarily complicate state management (without Redux)?

I'm fairly new to React to trying to wrap my head around routing via React Router while also passing required data to components. I will probably eventually incorporate Redux in my app, but I'm trying to avoid it initially.
It seems like using React Router as opposed to serving individual pages from the server means having to store state data in the App.js component since that's where the Router exists.
For example if I'm on site.com/x and I want to navigate to site.com/y and /x looks like this:
<div>
<XOuter >
<XInner />
</XOuter>
</div>
And App.js looks like this:
<BrowserRouter>
<Route exact path="/x" component={X} />
<Route exact path="/y" component={Y} />
</BrowserRouter>
... if the GET request is being called from XInner and the results will inform the content of /y, XInner will have to pass the response all the way back to App.js to properly render /y.
It seems like this could get messy quickly. Is there any way to avoid it?
This isn't as bad as you think, for two reasons:
If you use React Router's <Link> component to create links instead of using <a> directly, it will add event handlers that cancel the link's actual navigation and instead use history.pushState to do the navigation. To the user, they think they're on the new page (the URL bar shows this), but no GET request ever actually happened to load it.
React Router's paths are parsed via path-to-regexp. This lets you add parameters to the URL and then extract them from the router's props. You can also put data in the query string and then parse it later. This will let you pass state from one page to another without using any top-level React state, with the added benefit of making the browser's history and URL copying automatically work right.
The data is stored in the path instead of App.js. Path should be converted to props through pure function so the same path is always converted to the same props. That's the external state that chooses a <Route /> and sets its props.
Root of your problems lies in this design:
if the GET request is being called from XInner and the results will inform the content of /y, XInner will have to pass the response all the way back to App.js to properly render /y
Remove the if the GET request is being called from XInner... and all your concerns become moot.
Component A should not be responsible for fetching data for Component B. If /y needs data, fetch the data in Y's componentdidmount.
Example code showing the concept
fetchData(){
fetch(...) // or axios or whatever
.then(() => {
this.setState({
data: 'Hello World'
})
})
}
componentDidMount(){
this.fetchData()
}
render() {
return(
<div>
{this.state.data}
</div>
)
}

Route matching in React

I want to make a page with url users/:userId where userId will be (java, cpp, c).So in home page if click on java button it will open a page wit url users/java, similarly for c and cpp.All these pages will have same layout with different data.For this I made a simple component as Lang.jsx
export class Lang extends Component{
render() {
const { data } = this.props;
return (
<div>
<h1>{data.lang}</h1>
</div>);
}
}
In Router class we have to write something as
<Route path="users/:userId" component={Lang} />
But it is not clear how to pass userId in router and
load Lang for each url?
Use Link for these java, cpp, c, like this:
<Link to='users/java'>Java</Link>
<Link to='users/cpp'>Cpp</Link>
<Link to='users/c'>C</Link>
If you are using history.push() then write it like this:
hashHistory.push('users/java');
Note: You can make that userid optional because if you don't pass userid it will not match to any route, write it like this to make it optional:
<Route path="users/(:userId)" component={Lang} />
Now if you don't pass anything then also it will open the Lang page.
One more thing since you are passing the parameter by name userId, so it will available by this.props.params.userId not by this.props.data.lang.
I'm not completely sure what you're trying to do here, but if you have a look at this.props inside of lang, there will be a params key (this.props.params), which will be an object containing the userId.
eg:
this.props = {
params: {
userId: '1234',
},
}
This means that when you go to /user/1234, the userId key will have the value of 1234.
I'm not really sure what you're expecting the value of data.lang to be. Are you wrapping the routes in another route that provides that prop? What library are you using for lang?

Categories