I want to programmatically pass data between pages when navigating with useRouter's push() method. The following code redirects me to the url http://localhost:3000/[object%20Object], but I was expecting it to take me to http://localhost:3000/home?userid=deepeshdm&orderid=12345. Why does it do this, and how do I fix it?
// app/page.js
"use client"
import { useRouter } from "next/navigation";
export default function Home() {
const router = useRouter();
const handleClick = () => {
router.push({
pathname: '/home',
query: { userid: 'deepeshdm', orderid: '12345' },
});
};
return (
<>
<h1 align="center"> Root Page </h1> <br/>
<button onClick={handleClick}> GO HOME </button> <br/>
</>
)
}
It appears for Next.js 13, router.push() only accepts a string.
It looks like they dropped pathname and query for read-only hooks.
The way around this is to use template literal string interpolation:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
router.push(`/home?userid=${userid}&orderid=${orderid}`);
I hope this helps.
Related
Every time I reload my page (F5) my website lost the running query parameter.
Ex:
http://127.0.0.1:5173/subscriber/123456789?searchType=document
When I manually reload the page, it changes automatically to:
http://127.0.0.1:5173/subscriber/123456789
And then I lost the reference on which type of information my code need to search.
Below is my component:
SearchSubscriber.jsx
import React, { useState } from "react";
import { Outlet, useNavigate, createSearchParams } from "react-router-dom";
export default function SearchSubscriber() {
const navigate = useNavigate();
const [searchProperties, setSearchProperties] = useState({ type: "document", value: "" });
async function handleSearch(event) {
event.preventDefault();
navigate({
pathname: searchProperties.value,
search: createSearchParams({
searchType: searchProperties.type,
}).toString(),
});
}
return (
<>
<form onSubmit={handleSearch}>
<select
value={searchProperties.type}
onChange={(e) => setSearchProperties({ type: e.target.value, value: "" })}>
<option value="document">Document</option>
<option value="mail">E-Mail</option>
</select>
<br />
<input
type="text"
value={searchProperties.value}
onChange={(e) => setSearchProperties({ ...searchProperties, value: e.target.value })}
/>
<button type="submit">Search</button>
</form>
<Outlet />
</>
);
}
Subscriber
import { useParams, useSearchParams } from "react-router-dom";
export default function Subscriber() {
const { key } = useParams();
const [queryParameters] = useSearchParams();
return (
<>
Searching {queryParameters.get("searchType")} for {key}
</>
);
}
Can someone help me?
Thanks a lot.
You are storing the search parameter in state const [searchProperties, setSearchProperties] = useState({ type: "document", value: "" });. Evertime you refresh a react app the state returns to default. In your case the values are empty. To avoid this you can use react-persist to ensure the values stay in state even after a refresh or store the values in localstorage and use the useEffect hook to pull any values stored in the localstorage before the component is mounted
EDIT
If you've set the route to /subscriber/123456789 or /subscriber/:id, the URL will default to that on refresh as the query params are only added to the route on submit
The version of react-router-dom is v6 and I'm having trouble with passing values to another component using Navigate.
I want to pass selected rows to another page called Report. But, I'm not sure I'm using the right syntax for navigate method and I don't know how to get that state in the Report component.
Material-ui Table: I'm trying to use redirectToReport(rowData) in onClick parameter.
function TableRows(props){
return (
<MaterialTable
title="Leads"
columns={[
...
]}
data = {props.leads}
options={{
selection: true,
filtering: true,
sorting: true
}}
actions = {[{
position: "toolbarOnSelect",
tooltip: 'Generate a report based on selected leads.',
icon: 'addchart',
onClick: (event, rowData) => {
console.log("Row Data: " , rowData)
props.redirect(rowData)
}
}]}
/>
)}
LeadTable component
export default function LeadTable(props) {
let navigate = useNavigate();
const [leads, setLeads] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl(url) {
const response = await fetch(url);
const json = await response.json();
setLeads(json[0]);
setLoading(false);
}
useEffect(() => {
fetchUrl("http://localhost:5000/api/leads");
}, []);
function redirectToReport(rowData) {
navigate('/app/report', { state: rowData }); // ??? I'm not sure if this is the right way
}
return(
<div>
<TableRows leads={leads} redirect={redirectToReport}></TableRows>
</div>
)}
Report component
export default function ReportPage(state) {
return (
<div>
{ console.log(state) // This doesn't show anything. How to use the state that were passed from Table component here?}
<div className = "Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);}
version 6 react-router-dom
I know the question got answered but I feel this might be helpful example for those who want to use functional components and they are in search of passing data between components using react-router-dom v6.
Let's suppose we have two functional components, first component A, second component B. The component A wants to share data to component B.
usage of hooks: (useLocation,useNavigate)
import {Link, useNavigate} from 'react-router-dom';
function ComponentA(props) {
const navigate = useNavigate();
const toComponentB=()=>{
navigate('/componentB',{state:{id:1,name:'sabaoon'}});
}
return (
<>
<div> <a onClick={()=>{toComponentB()}}>Component B<a/></div>
</>
);
}
export default ComponentA;
Now we will get the data in Component B.
import {useLocation} from 'react-router-dom';
function ComponentB() {
const location = useLocation();
return (
<>
<div>{location.state.name}</div>
</>
)
}
export default ComponentB;
Note: you can use HOC if you are using class components as hooks won't work in class components.
Your navigate('/app/report', { state: rowData }); looks correct to me.
react-router-v6
If you need state, use navigate('success', { state }).
navigate
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}
Your ReportPage needs to be rendered under the same Router that the component doing the push is under.
Route props are no longer passed to rendered components, as they are now passed as JSX literals. To access route state it must be done so via the useLocation hook.
function ReportPage(props) {
const { state } = useLocation();
console.log(state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
If the component isn't able to use React hooks then you still access the route state via a custom withRouter Higher Order Component. Here's an example simple withRouter HOC to pass the location as a prop.
import { useLocation, /* other hooks */ } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const location = useLocation();
// other hooks
return (
<WrappedComponent
{...props}
{...{ location, /* other hooks */ }}
/>
);
};
Then access via props as was done in pre-RRDv6.
class ReportPage extends Component {
...
render() {
console.log(this.props.location.state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
}
2 things (just a suggestion):
Rather than a ternary use &&
{location && <div>{location.state.name}</div>}
Why are you checking location and rendering location.state.name? I would use the check on the data you are fetching or make sure the data returns null or your value.
On Sabaoon Bedar's Answer, you can check if there is any data or not before showing it :
Instead of this <div>{location.state.name}</div>
Do this { location != null ? <div>{location.state.name}</div> : ""}
if you want to send data with usenavigate in functional component you can use like that
navigate(`/take-quiz/${id}`, { state: { quiz } });
and you can get it with uselocation hook like this
const location = useLocation();
location.state.quiz there is your data
But you cannot get this data in props it;s tricky part ;)!!
on SABAOON BEDAR answer,
from component A: navigate('/', {state:"whatever"}
in component B: console.log(location.state) //output = whatever
I have followed the suggestion on Pass props in Link react-router
I'm sending Link value to the ReactJS functional component and while accessing it returning empty, I'm sure missing something basic here.
This is my Route path
<Route path="/report/:id">
<ReportPage />
</Route>
The report page which is trying to get value from either query param or state param both returns empty
const ReportPage = (props) => {
const { query } = useParams();
const { hello } = useState()
console.log(query) // Return Empty for Try #2
console.log(hello) // Return Empty for Try #1
console.log(this.props.match.params.hello) // Undefined for
return (
<DashboardLayout>
<h2> AMI Test Reports </h2>
</DashboardLayout>
)
}
DashbaordPage which is Linking
Tried 1 Using the state
<Link
to={{
pathname: "/report/1",
state: {
hello: "Hello World"
}
}}>
Press Me
</Link>
Tried #2 using the query
<Link
to={{
pathname: "/report/1",
query: "test"
}}>
Press Me
</Link>
What is the right way to access the Value passed in the Link in another functional component.
useParams returns an object containing all of the available parameters. What you currently have is useParams returning an object that contains a params property which is incorrect.
what you want:
const { id } = useParams();
As for the location state, you'd get that from useLocation, not useState.
const { state } = useLocation()
I'm wondering if anybody can point me in the right direction. I'm following the dynamic routes documentation on Next JS site - https://nextjs.org/docs/routing/dynamic-routes
Currently I'm rendering all products on the products page. I'm using getServersideProps to make the API call. Here is the products page:
import Link from "next/link";
const Products = ({ data }) => {
const products = data;
return (
<>
{products.map(({ id, name, seo: { description } }) => (
<div className="product" key={id}>
<h2>{name}</h2>
<p>{description}</p>
<Link href={`/products/${permalink}`}>
<a>View</a>
</Link>
</div>
))}
</>
);
};
export async function getServerSideProps() {
const headers = {
"X-Authorization": process.env.CHEC_API_KEY,
Accept: "application/json",
"Content-Type": "application/json",
};
const res = await fetch("https://api.chec.io/v1/products", {
method: "GET",
headers: headers,
});
const data = await res.json();
if (!data) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: data, // will be passed to the page component as props
};
}
export default Products;
In the Link I am using the permalink to direct people to a single product
<Link href={`/products/${permalink}`}>
<a>View</a>
</Link>
This is set up as products/[name].js in the structure, with index.js being the all products page.
Now on my single product page, [name].js I would like to run another getServersideProps to call the API and get the product - but I want to use the product id however I'd only like to display the permalink/slug in the URL.
Here is the [name].js page:
import { useRouter } from "next/router";
const Product = () => {
const router = useRouter();
console.log(router);
return <p></p>;
};
export default Product;
Of course this is just logging out the Object with what I can access. The query shows [name]: "product-name", which is great - I could now make a call for all products and filter the product the that matches this slug but I want to use the product id instead. Commerce.js has an request where I can request a single product just by using its id. How would I go about this?
Thanks.
I think you'll have a problem with SSR, if you want to pass the id and only show the slug in the URL how will SSR know of your ID if no one will be there to pass it?
You can try something like this, but it will work only in SPA mode.
<Link href={{ pathname: '/products' query: { prodId: id} }} as={permalink} />
I think you can't hide the ID form URL if you want SSR to work.
Edit:
If you want a workaround for improved SEO you could have a structure like products/[...slug].js and then using the catch-all your path will be like /products/342/product-name is a compromise but it will work with SSR.
I am making a simple Next Js application which has only two pages..
index.tsx:
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
As per the above code, on click Go to the about page it goes to about page and using query I also receive the passed query values in about page.
about.tsx
import React from "react";
import Router, { withRouter } from "next/router";
function About({ router: { query } }: any) {
return (
<div>
Candidate Id: <b> {query.candidateId} </b>
</div>
);
}
export default withRouter(About);
This displays the value but on page refresh while we are in /about page, the candidateId received gets disappeared.
Requirement: Kindly help me to retain the query value passed down from one page to another page even on page refresh.
Note: As per my requirement I should not display the canidateId on url while navigating and hence I am using as approach.. I know I can achieve it if I remove as but I cannot remove that here in index page while navigating.. Reason is this will lead to displaying candidateId in the url which is not intended..
Tried this solution: https://stackoverflow.com/a/62974489/7785337 but this gives empty query object on refresh of page.
Stuck for very long time with this please kindly help me.
If you do not want to use the query parameter you may need to create a "store" that saves your variable that persist throughout your pages.
Sample code as follows.
//candidatestore.js
export const CandidateStoreContext = createContext()
export const useCandidateStore = () => {
const context = useContext(CandidateStoreContext)
if (!context) {
throw new Error(`useStore must be used within a CandidateStoreContext`)
}
return context
}
export const CandidateStoreProvider = ({ children }) => {
const [candidateId, setCandidateId] = useState(null);
return (
<CandidateStoreContext.Provider value={{ candidateId, setCandidateId }}>
{children}
</CandidateStoreContext.Provider >
)
}
Then you need to wrap the Provider around your app like
<CandidateStoreProvider><App /></CandidateStoreProvider>
This way you can use anywhere as follows both in your index page and your about page.
const { candidateId, setCandidateId } = useCandidateStore()
UseContext
In your codes, it should probably look something like that.
import React from "react";
import Link from "next/link";
import { useCandidateStore } from './candidatestore'
export default function Index() {
const { candidateId, setCandidateId } = useCandidateStore()
useEffect(() => {
setCandidateId(thecandidateId)
})
return (
<div>
<Link
href={{
pathname: "/about",
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
function About({ router: { query } }: any) {
const { candidateId, setCandidateId } = useCandidateStore()
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
Update to Next.JS 10. It comes with Automatic Resolving of href which fixes your problem.
Try to delete the as="about" and then navigate again to the "about" page, the issue should be gone.
Codesandbox
My best bet would be to store the candidateId in an encrypted session on the client side. You could read/verify cookies in getServerSideProps() and pass their contents to the page component. If this sounds feasible, I'd recommend checking out the next-iron-session.
Another approach would be to check if candidateId exists in the query object in getServerSideProps(). If it does then pass it straight to the page component. If not, either get it elsewhere, redirect, or pass some default value. Append the following starter code to your about.tsx:
/* ... */
export function getServerSideProps({ query }: any) {
// if query object was received, return it as a router prop:
if (query.candidateId) {
return { props: { router: { query } } };
}
// obtain candidateId elsewhere, redirect or fallback to some default value:
/* ... */
return { props: { router: { query: { candidateId: 8432 } } } };
}
index.tsx file
Keep the code same as it is.
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
AboutUs.tsx
Code starts from here
Adding router as a dependency in the useEffect the issue should get solved.
import Router, { useRouter } from "next/router";
import React, { useState, useEffect } from 'react';
function About({ router: { query } }: any) {
const route = userRouter();
const [candidateId, setCandidateid] = useState();
useEffect(() => {
const {candidateId} = router.query
if(candidateId) {
setCandidateid(candidateid)
}},[router]) //Here goes the dependency
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
export default (About);