React Router Dom V6 - Lost Search Parameter on Refresh - javascript

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

Related

how to pass data between pages in Nextjs 13 with router.push()?

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.

In React Router V6, can I still get RouteComponentProps (or access to history and location) in class components? [duplicate]

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

Query values lost on page refresh in Next js? [Example given]

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);

How to properly set multiple states in useEffect

I'm currently learning React/hooks/redux. To do so, I'm building a react app that takes in data from a climate API.
The problem I'm having is correctly setting state for a couple of items in useEffect. One state relies on the other, so I'm trying to figure out how to properly call useEffect so I don't get infinite loops and follow best-practices.
A little background before the code included below:
-The user creates a project, and selects a city. This produces a cityId that I'm storing in my "project" state.
-On the user's dashboard, they can click a project that sends the project ID in a queryString to my ClimateData component.
-ClimateData passes the project ID queryString to the "getProjectByID" redux action to get the project state, including it's cityId.
-ClimateData includes the IndicatorList component, which brings in a list of all the climate data breakouts. I want the user to click one of these list items and have ClimateData's "indicatorByCityData" state set. So I passed ClimateData's setState function to IndicatorList and have the list call with onClicks. Is there a better way I should do this?
-On ClimateData, once I have the project's cityId, and the selected item from IndicatorList, I need to call "getIndicatorByCity" and pass both the cityId and indicator to have the result saved in the "indicatorByCityData" state
I keep trying to change how my ClimateData's useEffect is written, but I'm either getting infinite loops or errors. How can I best change this to set both states and follow best practices?
The redux actions and reducers have been tested elsewhere and work fine, so for brevity, I'll exclude them here and just focus on my ClimateData and IndicatorList components:
import React, { Fragment, useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import IndicatorList from './IndicatorList';
import Spinner from '../layout/Spinner';
import { getProjectById } from '../../actions/projects';
import { getIndicatorByCity } from '../../actions/climate';
const ClimateData = ({
getProjectById,
getIndicatorByCity,
project: { project, loading },
auth,
match
}) => {
const [indicatorByCityData, setIndicatorByCityData] = useState({});
const nullProject = !project;
useEffect(() => {
if (!project) getProjectById(match.params.id);
// Once we have the cityID, set the indicatorByCityData state, with a default selected Indicator
if (!loading) setIndicatorByCityData(getIndicatorByCity(project.cityId));
}, []);
// Get the selected indicator from IndicatorList and update the indicatorByCityData state
const setIndicator = indicator => {
setIndicatorByCityData(getIndicatorByCity(project.cityId, null, indicator));
};
return (
<Fragment>
{project === null || loading || !indicatorByCityData ? (
<Spinner />
) : (
<Fragment>
<Link to='/dashboard' className='btn btn-light'>
Back To Dashboard
</Link>
<h1 className='large text-primary'>{`Climate Data for ${project.city}`}</h1>
<IndicatorList setIndicator={setIndicator} />
</Fragment>
)}
</Fragment>
);
};
ClimateData.propTypes = {
getProjectById: PropTypes.func.isRequired,
getIndicatorByCity: PropTypes.func.isRequired,
project: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
project: state.projects,
auth: state.auth
});
export default connect(mapStateToProps, { getProjectById, getIndicatorByCity })(
ClimateData
);
/******************************************************************/
import React, { useEffect, Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Spinner from '../layout/Spinner';
import { getIndicatorList } from '../../actions/climate';
const IndicatorList = ({
getIndicatorList,
auth: { user },
climateList: { indicatorList, loading },
setIndicator
}) => {
useEffect(() => {
getIndicatorList();
}, [getIndicatorList]);
return loading ? (
<Spinner />
) : (
<Fragment>
{indicatorList.length > 0 ? (
<Fragment>
<ul>
{indicatorList.map(indicator => (
<li key={indicator.name}>
<a href='#!' onClick={() => setIndicator(indicator.name)}>
{indicator.label}
</a>
<br />- {indicator.description}
</li>
))}
</ul>
</Fragment>
) : (
<h4>No climate indicators loaded</h4>
)}
</Fragment>
);
};
IndicatorList.propTypes = {
auth: PropTypes.object.isRequired,
climateList: PropTypes.object.isRequired,
setIndicator: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
climateList: state.climate
});
export default connect(mapStateToProps, { getIndicatorList })(IndicatorList);

How to pass data returned from server and then transition to another route with returned data?

Hi, I am new to react. I made a server call and then successfully got a response. Now, I am trying to pass the same response to another route and display there. I am unable to understand, how do we do this in react, like in Ember there's a method this.transitionTo('routepath', {data}) and then this data is available in routepath in models(params). How is this possible in react? I am bound to not using any Redux or any other state container.
My code is below:
import React, { Component } from 'react'
import axios from 'axios'
class Ernform extends Component {
constructor (props) {
super();
this.state = {
category: '',
zipcode: '',
age: '',
gender: '',
key: '',
data: null
};
}
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
}
getPolicies = (e) => {
e.preventDefault();
const { category, zipcode, age, gender, key } = this.state;
axios.post(`http://localhost:4000/api/v1/quote`, { category, zipcode, age, gender, key })
.then(response => {
this.setState({data: response}); // I am setting response from server to the state property data
this.props.history.push('/details', {data: this.state.data})//I am trying to pass the data and trying to transition here.
});
}
render() {
const { category, zipcode, age, gender } = this.state;
return (
<div>
<form onSubmit={this.getPolicies}>
<label>
Category: <input type="text" name="category" value={category} onChange={this.onChange}/>
</label>
<label>
Zipcode: <input type="text" name="zipcode" value={zipcode} onChange={this.onChange}/>
</label>
<label>
Age: <input type="text" name="age" value={age} onChange={this.onChange}/>
</label>
<label>
Gender: <input type="text" name="gender" value={gender} onChange={this.onChange}/>
</label>
<button className="btn btn-primary btn-lg lead" type="submit">Get Policies</button>
</form>
</div>
);
}
}
export default Ernform
My router is this:
import React from 'react'
import { Switch, Route } from 'react-router-dom'
import Ernform from './Ernform';
import Ernentry from './Ernentry'
import Erndetails from './Erndetails';
const Routing = () => (
<main>
<Switch>
<Route exact path='/' component={Ernentry}/>
<Route path='/auto' component={Ernform}/>
<Route path='/life' component={Ernform}/>
<Route path='/details' component={Erndetails}/> // added this route.
</Switch>
</main>
)
export default Routing
My details route component is this
import React, { Component } from 'react'
class Erndetails extends Component {
constructor(props) { // Where would i get the data i transition with from that route.
console.log(props);
super()
}
render() {
return (
<div></div>
)
}
}
export default Erndetails
Thanks a lot guys in Advance.
There are two solutions for your problem,
I) First solution is to pass the data in the push object along with the route in this manner,
this.props.history.push({
pathname: '/details',
state: { detail: response.data }
})
instead of what your passing.
In your details route component you can get fetch the data by using props.location.state.detail in your constructor or this.props.location.state.detail in componentDidMount life-cycle.
2) Second solution would be the store it in the local-storage using localStorage.setItem('token', data) and then access it using localStorage.getItem('token')
I don’t think react-router allows it. My advise will be to store your data in LocalStorage with LocalStorage#setItem method and after in your next component retrieve it with LocalStorage#getItem.
You can also use Redux for your state and share data with it between screen but as you are beginner it’ll take you more time to learn it.
You can also use Context API of React but as Redux it’ll take you more time even if it is easier than Redux.

Categories