How to remove scroll position with react-router v6 - javascript

I have a react application and I am struggling to remove the previous page scroll-position.
Please I would appreciate it if you understand my problem before concluding.
I do not want to preserve the scroll history. I want it to always go back to the top of the page.
I have used the solution below to make the application scroll back to the top of the page whenever a user navigates to a new page.
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
const ScrollToTop = ({ children }) => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return children || null;
};
export default ScrollToTop;
The above code is working very fine with no issues. What is not working for me is:
When a user clicks a Button I created using Link from react-router-dom they are navigated to the new page (correct page), but the scroll-position is of the previous page (the page they are coming from).
I have tried using these hooks.
useLayoutEffect
withRouter (is removed from react-router v6)
useHistory (is removed from react-router v6
useNavigate
They are not working.
How can I resolve this? Or is there something I am missing?
I have also tried implementing the solution below. But wont work.
import { useEffect } from 'react';
import { withRouter } from 'react-router-dom';
function ScrollToTop({ history }) {
useEffect(() => {
const unlisten = history.listen(() => {
window.scrollTo(0, 0);
});
return () => {
unlisten();
}
}, []);
return (null);
}
export default withRouter(ScrollToTop);

You can scroll to Top every time the user enters that screen, with focus hook https://reactnavigation.org/docs/function-after-focusing-screen/.

After 4 hours of research and trying and errors. I have found the solution to this.
In react-router v6. You do not need to your window. on your useEffect You just need to have your code like this:
useEffect(() => {
document.body.scrollTo(0, 0);
});
I found the answer here, and researched more on it.

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 invoke a function on every redirect using react-router-dom

I want to check for cookies every time a user gets redirected to a different Route. What would be the most conventional way to do this besides coding the function each time at the top of the main component which each Route loads?
You can use the useLocation and the useEffect hook:
function App() {
let location = useLocation();
useEffect(() => {
//do your stuff
}, [location]);
return (
// ...
);
}

Update Router query without firing page change event in Next JS

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.

How to change scroll position right before navigating to new routes in React?

I already have a working solution with react hooks that scrolls to the top of the page on route change but unfortunately this is not working for my use case.
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export default function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
return () => window.scrollTo(0, 0);
}, [pathname]);
return null;
}
The problem is that i'm implementing infinite scrolling in some of my routes with the react-infinite-scroll-component package and on these routes an API call gets dispatched on componentDidMount hook to fetch initial data and also further API calls are triggered when the scroll position is midway through existing data.
The scroll position change seems to be happening right after the new component is rendered which results in simultaneous duplicate API calls. But if the scroll position is already at the top before changing routes then everything works fine.
I need a way to make sure that right before changing routes the scroll position goes to the top. What is the best way to do this? I have looked at almost every single post regarding scroll positioning with react-router but i can't seem to wrap my head around it.
Any help will be appreciated.

Stop Audio on Route Change in React?

everyone. I'm still getting used to React and React Router, and this is one thing I have no figured out.
So, I have an app that plays a video (muted) and an audio track at the same time. I am using React Player to play both. My view looks like this (VideoPlayer is the react-player tag):
<div>
<VideoPlayer url={this.props.audio} playing={this.state.playing} />
<VideoPlayer url={this.props.video} playing={this.state.playing} />
</div>
This setup has worked for me, and I am able to play and control them both via a common state. I can stop them via an event handler I hooked up to a button:
handleStop() {
this.setState({playing: false})
}
And this works as well.
The issue, however, is that once I navigate to a different route, the audio (and presumably the video) remains playing in the background. Actually, let me rephrase, the audio restarts in the background when I change routes.
After reading this page from the react-router docs, I have tried to include logic to call handleStop in various lifecycle events, yet none of them does the trick. So far, I have tried putting calls to handleStop in componentWillReceiveProps, componentWillUpdate, componentDidUpdate and componentWillUnmount.
The closest I got was putting the call in componentWillUnmount, but I always receive an error about setting the state of an unmounted component (which doesn't make sense either, if this is called before unmounting?).
So, by any chance, does anybody know where to put my call to handleStop?
Thanks in advance.
I know this question is over 4 years old, but I had this problem today and wanted to share my way of solving it...
import React, { useRef, useEffect } from 'react';
import waves from '../audio/waves.mp3';
const RockyCoast = (props) => {
// the audio variable needs to be stored in a ref in order to access it across renders
let audio = useRef();
// start the audio (using the .current property of the ref we just created) when the component mounts using the useEffect hook
useEffect(() => {
audio.current = new Audio(waves)
audio.current.play()
}, [])
// Stop the audio when the component unmounts
// (not exactly what you asked re React Router, but similar idea)
useEffect(() => {
return () => {
audio.current.pause()
console.log("in cleanup")
}
}, [])
...
return (
<>
...
</>
)
}
export default RockyCoast;
Some of the key reasons this works properly is because we're declaring the audio variable as a React ref, so that we can access it again to pause it on unmount. It's also important that the dependency arrays for the two useEffect calls are the same (in my case they were empty, in order for them to act like componentDidMount and componentWillUnmount).
The question was asking specifically about changing routes using React Router, so there may be a little more work required for your specific circumstance, but if like me you have one parent component being rendered for the route where we need this feature, like this:
<Route exact path="/habitat/rocky_coast">
<RockyCoast />
</Route>
(audio playing on the page, then stopping when we navigate to a different page), this solution works beautifully.
Hopefully this helps some other poor dev, who like me managed to create a project with a ton of audio clips playing over themselves lol!
In my own audio player I had created using useState when the user was in some exact views the audio continue to play, so I have solved this problem using recoil Atoms & useRecoilState and with react-router useLocation & useNavigate
AudioAtom.js:
import { atom } from "recoil";
const playingAudioState = atom({
key: "playingAudioState",
default: false,
});
export default playingAudioState;
Player.jsx:
import { useState, useEffect } from "react";
import { useRecoilState } from "recoil";
import playingAudioState from "./AudioAtom";
function Player() {
const [audio, setAudio] = useState(new Audio(url));
const [playing, setPlaying] = useRecoilState(playingAudioState);
useEffect(() => {
playing ? audio.play() : audio.pause();
}, [playing]);
...
return <>
...
</>
}
CustomRoutes.jsx:
import { useEffect} from "react";
import { useRecoilState } from "recoil";
import playingAudioState from "./AudioAtom";
import { ... , useLocation, useNavigate } from "react-router-dom";
function CustomRoutes() {
const navigate = useNavigate();
const location = useLocation();
const [playing, setPlaying] = useRecoilState(playingAudioState);
const routesWithoutAudio = ["/", "/example", "/hello"];
useEffect(() => {
if (playing && routesWithoutAudio.includes(location.pathname)) {
// if the audio is playing and the user is in a route with no audio we will set
// the state to false and refresh / reload de current page to not listen to it
setPlaying(false);
navigate(0 , { replace: true });
}
}, [location]);
return (
...Routes
)
I never found an answer, even after moving the logic to push the URL into handleStop. So I just changed page using window.location.href = '...'. Hacky, but it worked.

Categories

Resources