Reputation: 2658
I'm using Material-ui's Tabs, which are controlled and I'm using them for (React-router) Links like this:
<Tab value={0} label="dashboard" containerElement={<Link to="/dashboard/home"/>}/>
<Tab value={1} label="users" containerElement={<Link to="/dashboard/users"/>} />
<Tab value={2} label="data" containerElement={<Link to="/dashboard/data"/>} />
If I'm currenlty visting dashboard/data and I click browser's back button I go (for example) to dashboard/users but the highlighted Tab still stays on dashboard/data (value=2)
I can change by setting state, but I don't know how to handle the event when the browser's back button is pressed?
I've found this:
window.onpopstate = this.onBackButtonEvent;
but this is called each time state is changed (not only on back button event)
Upvotes: 104
Views: 284338
Reputation: 2289
kajkal's answer (on Jul 24, 2020 at 23:27) was the closest answer for me, but it required upgrading my react router version and I wanted to avoid making a large change.
Ultimately, interrupting the browser back button seems to be an anti-pattern and the suggested approach is to save user data in session storage/reload it; that way you do not degrade the UX by blocking browser functions. This was also too big a change for me.
The other solutions didn't work because of my react router version.
I did come across a snippet that works for me, which uses the default browser exit confirm dialog (tested on Chrome). It's worth noting that this event is triggered by browser back button but, crucially, also by browser forward button and browser refresh:
const shouldShowBrowserExitConfirmDialog = true;
window.onbeforeunload = function () {
if (shouldShowBrowserExitConfirmDialog) {
// The default message CANNOT be overridden
return "Changes that you made may not be saved.";
} else {
return null;
}
};
This is what the dialog looks like on chrome:
Upvotes: 1
Reputation: 584
Upcoming version 6 introduces useBlocker hook - which could be used to intercept all navigation attempts.
import { useBlocker } from 'react-router';
// when blocker should be active
const unsavedChanges = true;
let blocker = useBlocker(
({ historyAction }) => unsavedChanges && historyAction === "POP"
)
{ blocker.state === "blocked" ? (
<div>
<p>Are you sure you want to leave?</p>
<button onClick={()=> blocker.proceed()}>
Proceed
</button>
<button onClick={()=> blocker.reset()}>
Cancel
</button>
</div>
) : null }
Upvotes: 3
Reputation: 5541
To handle browser back button click/press after navigation, follow the following steps
STEP 1:
Create callback function to handle back press/click useBackButton
import { useEffect, useState } from "react";
const useBackButton = (callback) => {
const [isBack, setIsBack] = useState(false);
const handleEvent = () => {
setIsBack(true);
callback();
window.history.go(1);
};
useEffect(() => {
window.addEventListener("popstate", handleEvent);
return () => window.removeEventListener("popstate", handleEvent);
});
return isBack;
};
export default useBackButton;
STEP 2:
When click on browser back or forward button. if you want to redirect to 'home' page, you can replace '/' with your homepage route (e.g. '/home' or '/' etc.).
import
function in your respective component to handle back click
import React from "react";
import { useNavigate } from "react-router";
import useBackButton from "../hooks/useBackButton";
const MyPage = () => {
const navigate = useNavigate();
const toDo = () => {
navigate("/");
};
const isBackPressed = useBackButton(toDo);
return <div>My Page</div>;
};
export default MyPage;
Upvotes: 0
Reputation: 934
Using react-router made the job simple as such:
import { browserHistory } from 'react-router';
componentDidMount() {
this.onScrollNearBottom(this.scrollToLoad);
this.backListener = browserHistory.listen((loc, action) => {
if (action === "POP") {
// Do your stuff
}
});
}
componentWillUnmount() {
// Unbind listener
this.backListener();
}
Upvotes: 64
Reputation: 696
in NextJs we can use beforePopState function and do what we want such close modal or show a modal or check the back address and decide what to do
const router = useRouter();
useEffect(() => {
router.beforePopState(({ url, as, options }) => {
// I only want to allow these two routes!
if (as === '/' ) {
// Have SSR render bad routes as a 404.
window.location.href = as;
closeModal();
return false
}
return true
})
}, [])
Upvotes: 5
Reputation: 49
Add these 2 lines in to your componentDidMount().This worked for me
window.history.pushState(null, null, document.URL);
window.addEventListener('popstate', function(event) {
window.location.replace(
`YOUR URL`
);
});
Upvotes: 3
Reputation: 41
just put in componentDidMount()
componentDidMount() {
window.onbeforeunload =this.beforeUnloadListener;
}
beforeUnloadListener = (event) => {
event.preventDefault();
return event.returnValue = "Are you sure you want to exit?";
};
Upvotes: 3
Reputation: 749
If you are using React Router V5, you can try Prompt.
Used to prompt the user before navigating away from a page. When your application enters a state that should prevent the user from navigating away (like a form is half-filled out), render a <Prompt>
<Prompt
message={(location, action) => {
if (action === 'POP') {
console.log("Backing up...")
// Add your back logic here
}
return true;
}}
/>
Upvotes: 3
Reputation: 3851
Using hooks. I have converted @Nicolas Keller's code to typescript
const [locationKeys, setLocationKeys] = useState<(string | undefined)[]>([]);
const history = useHistory();
useEffect(() => {
return history.listen((location) => {
if (history.action === 'PUSH') {
if (location.key) setLocationKeys([location.key]);
}
if (history.action === 'POP') {
if (locationKeys[1] === location.key) {
setLocationKeys(([_, ...keys]) => keys);
// Handle forward event
console.log('forward button');
} else {
setLocationKeys((keys) => [location.key, ...keys]);
// Handle back event
console.log('back button');
removeTask();
}
}
});
}, [locationKeys]);
Upvotes: 8
Reputation: 795
Hooks sample
const {history} = useRouter();
useEffect(() => {
return () => {
// && history.location.pathname === "any specific path")
if (history.action === "POP") {
history.replace(history.location.pathname, /* the new state */);
}
};
}, [history])
I don't use history.listen because it doesn't affect the state
const disposeListener = history.listen(navData => {
if (navData.pathname === "/props") {
navData.state = /* the new state */;
}
});
Upvotes: 25
Reputation: 41
For giving warning on the press of browser back in react functional components. do the following steps
const [isBackButtonClicked, setBackbuttonPress] = useState(false);
window.history.pushState(null, null, window.location.pathname);
window.addEventListener('popstate', onBackButtonEvent);
define onBackButtonEvent Function and write logic as per your requirement.
const onBackButtonEvent = (e) => {
e.preventDefault();
if (!isBackButtonClicked) {
if (window.confirm("Do you want to go to Test Listing")) {
setBackbuttonPress(true)
props.history.go(listingpage)
} else {
window.history.pushState(null, null, window.location.pathname);
setBackbuttonPress(false)
}
}
}
In componentwillmount unsubscribe onBackButtonEvent Function
Final code will look like this
import React,{useEffect,useState} from 'react'
function HandleBrowserBackButton() {
const [isBackButtonClicked, setBackbuttonPress] = useState(false)
useEffect(() => {
window.history.pushState(null, null, window.location.pathname);
window.addEventListener('popstate', onBackButtonEvent);
//logic for showing popup warning on page refresh
window.onbeforeunload = function () {
return "Data will be lost if you leave the page, are you sure?";
};
return () => {
window.removeEventListener('popstate', onBackButtonEvent);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onBackButtonEvent = (e) => {
e.preventDefault();
if (!isBackButtonClicked) {
if (window.confirm("Do you want to go to Test Listing")) {
setBackbuttonPress(true)
props.history.go(listingpage)
} else {
window.history.pushState(null, null, window.location.pathname);
setBackbuttonPress(false)
}
}
}
return (
<div>
</div>
)
}
export default HandleBrowserBackButton
Upvotes: 4
Reputation: 5200
Most of the answers for this question either use outdated versions of React Router, rely on less-modern Class Components, or are confusing; and none use Typescript, which is a common combination. Here is an answer using Router v5, function components, and Typescript:
// use destructuring to access the history property of the ReactComponentProps type
function MyComponent( { history }: ReactComponentProps) {
// use useEffect to access lifecycle methods, as componentDidMount etc. are not available on function components.
useEffect(() => {
return () => {
if (history.action === "POP") {
// Code here will run when back button fires. Note that it's after the `return` for useEffect's callback; code before the return will fire after the page mounts, code after when it is about to unmount.
}
}
})
}
A fuller example with explanations can be found here.
Upvotes: 13
Reputation: 727
Using hooks you can detect the back and forward buttons
import { useHistory } from 'react-router-dom'
const [ locationKeys, setLocationKeys ] = useState([])
const history = useHistory()
useEffect(() => {
return history.listen(location => {
if (history.action === 'PUSH') {
setLocationKeys([ location.key ])
}
if (history.action === 'POP') {
if (locationKeys[1] === location.key) {
setLocationKeys(([ _, ...keys ]) => keys)
// Handle forward event
} else {
setLocationKeys((keys) => [ location.key, ...keys ])
// Handle back event
}
}
})
}, [ locationKeys, ])
Upvotes: 54
Reputation: 11
It depends on the type of Router you use in React.
If you use BrowserRouter from react-router (not available in react-router v4 though), as mentioned above, you can use the action 'POP' to intercept the browser back button.
However, if you use HashRouter to push the routes, above solution will not work. The reason is hash router always triggered with 'POP' action when you click browser back button or pushing the route from your components. You cant differentiate these two actions either with window.popstate or history.listen simply.
Upvotes: 1
Reputation: 105
You can use "withrouter" HOC and use this.props.history.goBack
.
<Button onClick={this.props.history.goBack}>
BACK
</Button>
Upvotes: -7
Reputation: 404
I used withrouter hoc in order to get history prop and just write a componentDidMount() method:
componentDidMount() {
if (this.props.history.action === "POP") {
// custom back button implementation
}
}
Upvotes: 4
Reputation: 864
Version 3.x of the React Router API has a set of utilities you can use to expose a "Back" button event before the event registers with the browser's history. You must first wrap your component in the withRouter()
higher-order component. You can then use the setRouteLeaveHook()
function, which accepts any route
object with a valid path
property and a callback function.
import {Component} from 'react';
import {withRouter} from 'react-router';
class Foo extends Component {
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave);
}
routerWillLeave(nextState) { // return false to block navigation, true to allow
if (nextState.action === 'POP') {
// handle "Back" button clicks here
}
}
}
export default withRouter(Foo);
Upvotes: 9
Reputation: 2658
here is how I ended up doing it:
componentDidMount() {
this._isMounted = true;
window.onpopstate = ()=> {
if(this._isMounted) {
const { hash } = location;
if(hash.indexOf('home')>-1 && this.state.value!==0)
this.setState({value: 0})
if(hash.indexOf('users')>-1 && this.state.value!==1)
this.setState({value: 1})
if(hash.indexOf('data')>-1 && this.state.value!==2)
this.setState({value: 2})
}
}
}
thanks everybody for helping lol
Upvotes: 25