Update Router query without firing page change event in Next JS - javascript

I want to change a URL query for the current page in Next JS without triggering the page change event. My use case is to simply remember the week being viewed in a calendar, similar to Google Calendar. Here's what I've tried:
import Calendar from '../components/Calendar'
import { formatDate } from '../utils'
class CalendarDashboardPage extends React.Component {
refreshQuery(date) {
const { pathname, asPath } = this.props.router
const d = formatDate(date) // YYYY-MM-DD
this.props.router.push(pathname, asPath, { query: { d }, shallow: true })
}
render() {
return <Calendar onDateChange={this.refreshQuery) />
}
}
export default withRouter(CalendarDashboardPage)
This almost seems to work OK, but because this.props.router.push triggers a page change in Next, it causes the page loading bar that I'm using (nprogress) to appear on screen. I've tried this.props.router.push({ query: { d } });, but no luck.
So my question: is there any way to change the router query without triggering events such as routeChangeStart(url)?

You can keep track of the previous pathname and do a conditional check in the routeChangeStart event handler to see if the pathname(without query) changes:
// _app.js
import Router from "next/router";
let previousPathname = null;
function handleRouteChange(url) {
const pathname = url.split('?')[0];
if (previousPathname && previousPathname !== pathname) {
console.log(`App is changing from ${previousPathname} to ${pathname}...`);
}
previousPathname = pathname;
};
Router.events.on("routeChangeStart", handleRouteChange);
...
This may not answer your question directly since it will still trigger Router.events but hopefully can help you with your use case.

Short version: you can't (at least AFAIK).
The first requirement to change the URL in the browser without reload the page is to do that in a single page application.
To achieve I'm afraid you need to drop next/router and start to use react-router, in this article you can find details about how to do a SPA with next using react router.
Once you have done, with this.props.history.push("/new/url") react router feature you can do what you are looking for.
Hope this helps.

My solution was to just use the browser's API.
refreshQuery(date) {
const dateString = formatDate(date) // YYYY-MM-DD
window.history.pushState('', '', `?d=${dateString}`)
}
This changes the date URL parameter when the user flips through the calendar, which can be snagged by componentDidMount() when the users refreshes/shares the URL.

Related

Navigate hook from React Router Dom lets me navigate to desired page when I reload but comes back to same page

I want user to navigate to Home page of website when user refreshed the page .
I am using React-Router-Dom library to navigate.
I set the paths to true when user navigate to "/textbox" page and append a event listener for detecting page reload but when I reload it actually navigate to homepage but comes back to "/textbox" page right after reloading. IDK why?
here it is teh code:
import { useNavigate, useLocation } from "react-router-dom";
const navigate = useNavigate();
const locations = useLocation();
useEffect(() => {
console.log(paths);
if (locations.pathname === "/textbox") {
setPaths(true);
window.addEventListener("beforeunload", onReload);
} else {
console.log("Not reloaded");
}
return () => {};
});
function onReload() {
if (paths) {
navigate("/", { replace: true });
}
}
I tried a lot of things to solve to detect reload in react but I guess its related to react-router-dom.
I will be gratefull if anyone can help me.
Well, you have two issues in here: first navigate refers to react-router-dom history, and not to browser history API, second useEffect, with no dependency array, is triggered at every render.
Because of the first issue react navigates to "/" in its own history, but, from browser point of view, user is refreshing the /textbox page, that's why you see the "/" for one moment (react moves to this path, literally "before unload") and "/textbox" then (browser accomplished user request to refresh the page with path "/textbox").
I suggest you the following change
import { useNavigate, useLocation } from "react-router-dom";
// ...
const navigate = useNavigate();
const locations = useLocation();
useEffect(() => {
// we need to add it just once
window.addEventListener("beforeunload", onReload);
return () => {
// let's remove it on component unmount
window.removeEventListener("beforeunload", onReload);
};
}, []); // only one time, not at every render
const onReload = event => {
if (locations.pathname !== "/") {
// this is the important point
// you need to prevent the default browser history API event
event.preventDefault();
navigate("/", { replace: true });
}
}
Note that to prevent default browser event you may need something like this Prevent refreshing / reloading a page with JavaScript

How to watch if the querystring changes within React Router v6?

I've a form with a drop down on it, when I select the drop down I push the value onto the querystring. When the url with that querystring is first hit it stores the querystring param into state and then uses it to populate the drop down. This all works as intended. My issue is triggering the form to see changes to the querystring while still on the same page.
If I'm already on the page but then click a react router Link to the same page but with a different query string parameter it doesn't catch/see that it has changed. I'm wondering how to watch the querystring for changes or if there is a better way to handle this.
I've found that you can listen to the history object that React-Router is meant to use under the hood but I've had little success with this and again I'm not sure if this is the right way to go https://github.com/ReactTraining/history/blob/master/docs/getting-started.md
I've tried adding the following to the page but the listener never seems to fire when I change the querystring (aka search) of the url. Wonder what I'm missing?
useEffect(() => {
// Listen for changes to the current location.
console.info("wiring up history listener");
let unlisten = history.listen(({ location, action }) => {
console.info("HISTORY CHANGED", location.search);
console.log(action, location.pathname, location.state);
// Plan was to update my state here if needed
});
return unlisten;
}, []);
Using react-router-dom v6.
If you want to listen for changes on the path's query string parameters you need to "listen" for changes to them from the location object. Use the useLocation hook to get the location object.
location
{
key: 'ac3df4', // not with HashHistory!
pathname: '/somewhere',
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: true
}
}
Listen for changes using effect hook.
const { search } = useLocation();
useEffect(() => {
// search query string changed
}, [search]);
Your code doesn't work because react-router uses a different instance of history so your custom listeners aren't fired when the change is made through react-router's Link (it gets handled by a different version of history). However, clicking on the browser's forward or back buttons should trigger your listener since this notifies all history instances.
The best tool for your use case is useSearchParams hook
const [searchParams] = useSearchParams();
useEffect(() => {
// code for handling search query change
}, [searchParams]);
Also, you might not need to use useEffect but treat searchParams as a source of truth for search data without creating another entity in the state.
react-router comes with all the hooks you need. Sounds to me like you want: useParams

Keep param consistent on location change ReactJs

I am using react router v4 to change location in ReactJs.
this.props.history.push("/profile");
<Link to="/profile" />
The above code works fine.
Now I want to keep a param consistent in URL http://localhost:3000?source=test by using the same code as above.
One approach is that I find all the occurrences in the code and add condition that if params source=test exist then append it to the the URL as well but this approach doesn't look fine to me as I have add condition on every redirect, Link and history.push
Second approach that I find is that use of listener on location update given by react router
In my Main Route file
class App extends Component {
componentDidMount() {
this.unlisten = this.props.history.listen((location, action) => {
if (/source=ep/.test(this.props.location.search)) {
location.search = _startsWith(location.search, "?") ? location.search + "&source=test" : "?source=test"
}
});
}
}
With this approach I can easily append the params in search query of react router but the param doesn't show up in URL.
the URL looks like this http://localhost:3000/profile and When I get search params from react-router console.log(this.props.location.search) it shows the param source=test and it's exactly what I want but In this case if user refreshes on this page the search params lost from react-router as well because it's not in the URL.
Can you guys help me to keep source=test consistent even in URL.

Use browser history state with next js

I am trying to implement custom scrolling with nextjs. I want to be able to save the scroll position of the page when a user leaves so when they return with popstate I can reset the scroll position.
The issue is that when I try to use replaceState with the history api next/router overrides what I put there with their own data. Next/router doesn't provide any type of history for their router and they don't put an id or anything that I can reference to use my own state. So I have no way to reference what page of the history stack I'm actually on. Next/router api docs are located here Nextjs Router API.
Here is a quick example of what I'm trying to do:
const setHistory = () => {
const { state } = window.history;
const newState = {
...state,
scroll: 'my custom scroll value'
};
history.replaceState(newState, '', state.url);
}
const handleRouteChangeStart = (url) => {
setHistory();
}
const handlePopstate = () => {
console.log(window.history)
}
window.addEventListener('popstate', handlePopstate);
Router.events.on('routeChangeStart', handleRouteChangeStart);
I have also tried to set the value to the options key that nextjs sets but I have inconsistent results with that as well meaning sometimes the value isn't set and it feels a little hacky.
Does NextJs really not allow you to have any kind of interaction with browser history or the ability to manipulate the window.history object?

React Router -- history push state not refreshing with new state object

When Promise.all resolves and the new activity is saved, the user should be routed to /activities to view their newly created activity. Everything works as expected, however I currently need to refresh /activities page (once) after being routed in order to view the new activity in the table.
const handleSaveActivity = e => {
e.preventDefault();
Promise.all([
addActivity(),
saveActivity()
]).then(() => {
props.history.push('/activities');
})
};
I'm not sure how to re-render the page automatically after pushing a new history state, so the user does not need to manually refresh the page to see the new state. Happy to provide more code snippets if I left out something critical.
Hi i must be a little late to answer this, but this issue can be due to the wrong use of useEffect, if you have lets say a todo list and you wanna fetch data with axios for example, it would look like this:
useEffect(()=>{
axios.get(`${YOUR_URL}/todos`)
.then((res)=>{
setTodos(todos=res.data)
})
},[])
now as you can see we have initial value of an empty array, so this is acting as a ComponentDidMount, what you might want is to re render the component after it gets a new value, so you want to have a ComponentDidUpdate effect, so you would just not initialize the value as an empty array, therefore it would look like this:
useEffect(()=>{
axios.get(`${YOUR_URL}/todos`)
.then((res)=>{
setTodos(todos=res.data)
})
})
Hope this helps someone, couse i landed here due to the same issue and came to solve it this way.
just to run this.setState({whateverKey:whateverValue})?
In your activities page (call it Activities component) you should call API to get the updated data every time browser hit this component URL.
With class based style, you should do it in componentDidMount life cycle hook
class Activities extends Component {
// ...
componentDidMount() { loadActivities() }
// ...
}
With function based style, you should do it in useEffect hook
import React, { useEffect } from 'react'
const Activities = () => {
useEffect(() => { loadActivities() });
}
https://github.com/supasate/connected-react-router Please use this package, it solves the problem.
This issue I've faced a few minutes ago...however I finally found the solution by manually using the vanilla javascript. => for refreshing the page you can use
=> window.location.reload(false); after using the push property.

Categories

Resources