I am building a Next.js 13 project with the /app directory. I have a problem - in the root layout, I have a permanent navbar component in which the component is imported from /components/Navbar.jsx. Basically inside the Navbar.jsx, I want to be able to access the slug parameter in url, for ex: localhost:3000/:slug in which I want the slug id. I have already defined a Next.js 13 page.jsx for that slug. But how do I get the slug id in the navbar component. I also don't want to use window.location.pathname because it doesn't change when the page routes to a different slug and only does when I refresh.
I have tried the old Next.js 12 method:
//components/navbar.jsx;
import { useRouter } from "next/navigation";
export default function Navbar () {
const router = useRouter();
const { slug } = router.query;
useEffect(() => {
console.log(slug);
}, []);
return <p>Slug: {slug}</p>
}
However it does not work.
To get the URL parameters in a Server Component in Next.js 13, you can use the searchParams argument of the Page function.
URL
localhost:3000/?slug
page.js
export default function Page({searchParams}) {
return <Navbar slug={searchParams}></Navbar>
}
navbar.js
export default function Navbar(props) {
return <p>Slug: {props.slug}</p>
}
More info: https://beta.nextjs.org/docs/api-reference/file-conventions/page
Another way is to use the hook useSearchParams.
From the documentation:
import { useSearchParams } from 'next/navigation';
export default function Page() {
const searchParams = useSearchParams();
// E.g. `/dashboard?page=2&order=asc`
const page = searchParams.get('page');
const order = searchParams.get('order');
return (
<div>
<p>Page: {page}</p>
<p>Order: {order}</p>
</div>
);
}
Had to use usePathname
import { usePathname } from 'next/navigation';
Related
I am stuck with a problem with passing data from one page to another page in next.js as I am building a basic news application in which I am fetching get requests from news api and I got results of 10 articles and I mapped them correctly but I want to pass the single article date to a new Page named singleNews. So How can I do it?
here is place where I am fetching all 10 articles:
export default function news({data}) {
// const randomNumber = (rangeLast) => {
// return Math.floor(Math.random()*rangeLast)
// }
// console.log(data)
return (
<>
<div>
<h1 className="heading">Top Techcrunch Headlines!</h1>
</div>
<div className={styles.newsPage}>
{ // here you always have to check if the array exist by optional chaining
data.articles?.map(
(current, index) => {
return(
<Card datas={current} key={index+current.author} imageSrc={current.urlToImage} title={current.title} author={current.author}/>
)
}
)
}
</div>
</>
)
}
export async function getStaticProps() {
const response = await fetch(`https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=${process.env.NEWS_API_KEY}&pageSize=12`)
const data = await response.json() // by default Article length is 104
// const articles = data.articles;
return{
props : {
data,
}
}
}
You can pass data to another page via Link component this way:
import Link from 'next/link'
<Link
href={{
pathname: '/to-your-other-page',
query: data // the data
}}
>
<a>Some text</a>
</Link>
and then receive that data in your other page using router:
import { useRouter } from 'next/router'
const router = useRouter();
const data = router.query;
Update - Next.js v13:
Some changes have been made on this version. Next.js added a new app directory feature which uses React Server Components by default. This feature is still experimental and it's not recommended for production just yet. Regular pages folder can still be used.
app directory feature can be enabled under the experimental flag on next.config.js file:
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
experimental: {
appDir: true
}
}
module.exports = nextConfig
Some changes have been implemented on the Link component and useRouter hook, also some new hooks have been added for client-side use. A brief list of these changes:
It's no longer needed to wrap an a tag with the Link component. The new Link component extends the HTML <a> element. <a> tag attributes can be added to Link as props. For example, className or target="_blank". These will be forwarded to the underlying <a> element on render.
The new useRouter hook should be imported from next/navigation and not next/router.
The query object has been removed and is replaced by useSearchParams().
Passing data from one page to another using app directory feature and Server Components:
import Link from 'next/link'
const SomePage = () => {
return (
<section>
<h1>Some page</h1>
<Link
href={{
pathname: '/anotherpage',
query: {
search: 'search'
}
}}
>
Go to another page
</Link>
</section>
)
}
export default SomePage
Receiving the data through searchParams prop:
const AnotherPage = ({ searchParams }) => {
console.log(searchParams.search) // Logs "search"
...
}
export default AnotherPage
On Client Components:
'use client'
import { useSearchParams } from 'next/navigation'
const SomeClientComponent = () => {
const searchParams = useSearchParams()
console.log(searchParams.get('search')) // Logs "search"
...
}
export default SomeClientComponent
This also works with page components on pages folder. Don't include the 'use client' directive in this case.
I am creating an ecommerce app with Nextjs and want to share data between pages. I know that we can't use props to pass data between the pages and so was looking into react context api. This is my first time using react context api. I've researched and found that you should add the Provider in the _app.js page in nextjs.
But this shares the data among all the pages. Plus my data is being retrieved by getStaticProps in the slug page of the app. I want to get this data into the checkout page of my app.
This is the context I have created:
import { createContext, useState, useContext } from 'react';
const productContext = createContext({} as any);
export const ProductProvider = ({ children }) => {
const [productData, setProductData] = useState('');
return <productontext.Provider value={{ productData, setProductData }}>{children}</productContext.Provider>;
};
export const useProduct = () => useContext(productContext);
_app.js
import { ReportProvider } from '../contexts/ReportContext';
export default function CustomApp({ Component, pageProps }) {
return (
<ReportProvider>
<Component {...pageProps} />
</ReportProvider>
);
}
I import this into the slug page and try to update the state from here
// [slug].js
import client from '../../client'
import {useProduct} from './productContext';
const Post = (props) => {
const {setProductData} = useProduct();
const { title = 'Missing title', name = 'Missing name' , price} = props
setProductData(title);
return (
<article>
<h1>{title}</h1>
<span>By {name}</span>
<button>
Buy Now
</button>
</article>
)
}
Post.getInitialProps = async function(context) {
const { slug = "" } = context.query
return await client.fetch(`
*[_type == "post" && slug.current == $slug][0]{title, "name": author->name, price}
`, { slug })
}
export default Post
However this productData is not accessible from another page and the react context state is not getting updated.
Any idea why this could be happening?
Once you've updated your context value. Please make sure you are using next/link to navigate between pages. Here is details about next/link
Im stuck at this thing. I want to get url parameter (/mypage/?title=my-title) from url and display it in the page.
Currently I tried client-only routes in gatsby
import path from "path"
exports.onCreatePage = ({ page, actions }) => {
const { createPage } = actions
if (page.path.match(/^\/headline\/*/)) {
createPage({
path: "/headline",
matchPath: "/headline/*",
component: path.resolve("src/templates/headline.tsx"),
})
}
}
// gatsby-node.js
Here is my template
import React from "react"
import { Router, RouteComponentProps } from "#reach/router"
type Props = RouteComponentProps<{
title: string
}>
const Test = ({ title = "Test title", location }: Props) => {
console.log(title)
console.log(location)
return <h1>Im test</h1>
}
const Headline = () => {
console.log("handle temp called")
return (
<Router>
<Test path="/compare/headline/:title" />
</Router>
)
}
export default Headline
// src/templates/headline.tsx
Nothing is rendering when I hit the /headline/?title=test-my-app
My component (Test) is not being called from Router tag.
Any help ?
I think the issue is simply that you have an extra "compare" in your URL in the router. If you replace the part inside Router with <Test path="headline/:title" /> it ought to work with the URL you are testing.
The router will return an empty page if the path does not match any of the paths specified. You can add a default route as a catch-all.
Normally you shouldn't need to create a router inside the page by the way, but I guess that depends on your use case.
I have looked around the docs to see if I can have _app.js read a slug (nothing mentioned about it). I need this slug to be added to an HTTP get request to grab the proper data that then then returns results to _app.js in which I can then use to pass props to all components and pages.
Example: when I go to http://localhost:3000/some-business-name , _app.js can grab the slug (some-business-name), do the request, and pass props to all components and pages in the project.
But what I am really struggling to do is get the App props to pass to all the rest of the pages inside the pages folder.
In my pages folder I have:
_app.js -- (what I need to pass props to all pages)
[slug].js -- (root page that used to detect slug and now I need for it to just receive props from _app.js)
success.js -- (need to receive props from _app.js)
error.js -- (need to receive props from _app.js)
I am using a data file that is an array of business data objects to which I use to test dynamic routing with.
I have looked in the NextJS docs and I am having an issue understanding how this can be done. I still need for the slug to exist, I just need help understanding how I can get _app.js to completely take over dynamic routing.
My code for _app.js is:
import React from 'react'
import App from 'next/app'
import { businesses } from '../data';
export default function MyApp({ Component, appProps }) {
return (
<Component appProps={appProps} />
)
};
MyApp.getInitialProps = async ({ appContext }) => {
const appProps = await App.getInitialProps(slug);
return {appProps};
};
App.getInitialProps = async (slug) => {
const business = businesses.filter((business) => {
return business.slug === slug;
});
return business[0];
};
Currently my [slug].js is:
import React from 'react';
import Head from 'next/head';
import LandingPage from '../components/landing-page';
export default function Slug(props) {
return (
<div>
<Head>
<title>Home</title>
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css' integrity='sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm' crossOrigin='anonymous' />
<link href='https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght#0,300;0,400;0,600;0,700;0,800;1,300;1,400;1,600;1,700;1,800&display=swap' rel='stylesheet' />
<script src='https://code.jquery.com/jquery-3.2.1.slim.min.js' integrity='sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN' crossOrigin='anonymous'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js' integrity='sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q' crossOrigin='anonymous'></script>
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js' integrity='sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl' crossOrigin='anonymous'></script>
</Head>
<LandingPage businessInfo={props.appProps}/>
<style global jsx>{`
body {
font-family: 'Open Sans', sans-serif;
}
`}</style>
</div>
);
};
Surprisingly I am able to receive App props on the LandingPage component in [slug].js but not in the success.js and error.js pages.
Any help is extremely appreciated! Thanks!
You can use something like this:
import React from 'react'
import App from 'next/app'
import { Router } from '../routes'
class MyApp extends App {
// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// static async getInitialProps(appContext) {
// // calls page's `getInitialProps` and fills `appProps.pageProps`
// const appProps = await App.getInitialProps(appContext);
//
// return { ...appProps }
// }
render() {
const { Component, appProps } = this.props
// Workaround for https://github.com/zeit/next.js/issues/8592
const { err } = this.props
const modifiedPageProps = { ...appProps, err }
return (
<div id="comp-wrapp">
<Component {...modifiedPageProps} />
</div>
)
}
}
export default MyApp
Better would do it this was
class NextDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
const { req } = ctx
// pass down everything u need there
return { ...initialProps, locale }
}
I have a signin page and layout component.Layout component has header.I don't want to show header in signin .and for that I want to get url pathname.based on pathname show the header .
import * as constlocalStorage from '../helpers/localstorage';
import Router from 'next/router';
export default class MyApp extends App {
componentDidMount(){
if(constlocalStorage.getLocalStorage()){
Router.push({pathname:'/app'});
} else{
Router.push({pathname:'/signin'});
}
}
render() {
const { Component, pageProps } = this.props
return (
//I want here pathname for checking weather to show header or not
<Layout>
<Component {...pageProps} />
</Layout>
)
}
}
please help
If you want to access the router object inside any functional component in your app, you can use the useRouter hook, here's how to use it:
import { useRouter } from 'next/router'
export default function ActiveLink({ children, href }) {
const router = useRouter()
const style = {
marginRight: 10,
color: router.pathname === href ? 'red' : 'black',
}
const handleClick = e => {
e.preventDefault()
router.push(href)
}
return (
<a href={href} onClick={handleClick} style={style}>
{children}
</a>
)
}
If useRouter is not the best fit for you, withRouter can also add the same router object to any component, here's how to use it:
import { withRouter } from 'next/router'
function Page({ router }) {
return <p>{router.pathname}</p>
}
export default withRouter(Page)
https://nextjs.org/docs/api-reference/next/router#userouter
You can use asPath property, that will give you the path (including the query) shown in the browser without the configured basePath or locale:
const { asPath } = useRouter()
Suppose the complete URL of a page is 'abc.com/blog/xyz' and the component file name matching with this route is './pages/blog/[slug].js'
useRouter() hook returns a route object, which has two properties to get the pathname.
One is asPath property, and
Another one is pathname property.
asPath property contains pathname extracted from the URL i.e. /blog/xyz
but pathname property contains the pathname of your project directory i.e. /blog/[slug].
Example Implementation
// .pages/blog/[slug].js
import { useRouter } from 'next/router';
const BlogSlug = () => {
const { asPath, pathname } = useRouter();
console.log(asPath); // '/blog/xyz'
console.log(pathname); // '/blog/[slug]'
return (
<div></div>
);
}
export default BlogSlug;
To fully use the SSR out-of-the-box provided by Next.js, you can use the context object provided in getInitialProps and which contains the pathname. You can then pass this pathname to be used as a props by your component.
For example:
class Page extends React.Component {
static getInitialProps({ pathname }){
return { pathname }
}
render() {
return <div>{this.props.pathname === 'login' ? 'good' : 'not good'}</div>
}
}
Might be late but just use router.pathname
function MyComp() {
const router = useRouter();
return (
<a className={router.pathname === '/some-path' ? 'currentCSS' : 'defaultCSS'}>
Some link
</a>
);
}
One cannot access the Router or the useRouter() options to access the current path in app.js file. This is not client side rendered and hence the only way to access you current path would be to pass it from your getInitialProps() or the getServerSideProps() call to your App component, and then access it there to develop your logic based on the current route.
My app needed to have multiple documents, so I also was looking for a way to get the path name, with nextjs, default document
This is a way that I found, which works for me.
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { LandingPage, WithSidePanels } from '../documents'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
console.log(this.props.__NEXT_DATA__.page)
if(this.props.__NEXT_DATA__.page === "/") return <LandingPage />
return (
<WithSidePanels />
)
}
}
export default MyDocument
So this.props.__NEXT_DATA__.page this is going to give you, the path name, "/", or "/contact" or whatever,
from the _document.js :)
For whom who are searching for an example:
import React, { Component } from "react";
import { withRouter } from 'next/router'
class Login extends Component {
constructor(props) {
super(props);
}
onClickHandler = (event) => {
this.props.router.push('/newPage')
}
render() {
return (
<div>
<p>Hello, {this.props.router.pathname}</p>
<button onClick={this.onClickHandler}>Click me!</button>
</div>
);
}
}
export default withRouter(Login);
Probably to avoid use import Router from 'next/router' in nextjs
you may use
import {useRouter} from 'next/router';