I have a parent component which maintains state for three 'form' components that render in sequence. It looks something like this:
<Parent>
{ renderFormBasedOnState() }
</Parent>
FormA renders, then when next is click FormB renders then FormC renders, all in the parent.
Previously I was using a React Router to do this, but the problem is, I don't want the user to be able to bookmark /formb or /formc, as that would be an invalid state.
I can do this with a switch statement, but then I lose forward / back button browser history ability - and I don't want to basically re-implement react-router in my component. What is the simplest way to go about this?
Haven't tried it for the back of the browser, but it could look something like this:
export default class tmp extends React.Component {
state = {
currentVisibleForm: 'A'
}
onBackButtonEvent = (e) => {
if(this.state.currentVisibleForm !== 'A') {
e.preventDefault();
//Go back to the previous visible form by changing the state
} else {
// Nothing to do
}
}
componentDidMount = () => {
window.onpopstate = this.onBackButtonEvent;
}
render() {
return (
<Parent>
{this.state.currentVisibleForm === 'A' &&
<FormA />
}
{this.state.currentVisibleForm === 'B' &&
<FormB />
}
{this.state.currentVisibleForm === 'C' &&
<FormC />
}
</Parent>
)
}
}
Tell me if it is of any help!
So I was able to get this working with the history api, however it may not be worth the effort to fine tune - I may revert. Managing state in two places is kind of dumb. Note this history object is the same from the application's 'Router' component, and doesn't conflict.
state = {
FormData: {},
action: 'Form_1'
}
componentWillMount() {
this.unlistenHistory = history.listen((location) => {
if (location.state) {
this.setState(() => ({
action: location.state.action
}));
}
});
history.push(undefined, {action: 'FORM_1'});
}
componentWillUnmount() {
this.unlistenHistory();
}
finishForm1 = () => {
const action = 'Form_2';
history.push(undefined, { action });
this.setState((prevState) => ({
// business stuff,
action
}));
};
renderCurrentState() {
switch(this.state.action) {
case 'FORM_1':
return <Form1 />
...
}
}
render() {
return (
<div>
{ this.renderCurrentState() }
</div>
);
}
Related
I am trying to style a Navbar Link depending on the current path in my React App, if the path is /create or /add it should change it's styling. Here is what I have so far in my header component:
<div
id="createLink"
className={this.state.createClassName}
onClick={() => this.handleModalToggle()}
>
CREATE
</div>
handleActiveLink= () => {
let path = this.props.location.pathname
if (path === "/add" | path === "/create") {
this.setState({createClassName: "nav-link-active"})
} else {
this.setState({ createClassName: "nav-link" })
}
};
componentDidMount() {
this.handleActiveLink()
}
This works but only after I refresh the page which makes sense but it's not what I want. So I am looking for a way to change the className before even rendered and get the path first (I am using withRouter from react-router-dom)
Issue appears to be you only check the path when the component mounts and not when it updates. You should also check in componentDidUpdate
handleActiveLink= () => {
let path = this.props.location.pathname;
if (path === "/add" || path === "/create") {
this.setState({createClassName: "nav-link-active"});
} else {
this.setState({ createClassName: "nav-link" });
}
};
componentDidMount() {
this.handleActiveLink();
}
componentDidUpdate() {
this.handleActiveLink();
}
In this case I instead recommend not storing such transient data in state, and simply derive it from the props and set as a className in the render function (or wherever you render it). This way it's computed each render when the UI is going to be updated by something and will always be up-to-date (i.e. you won't need to worry about lifecycle functions).
render() {
const { location: { pathname } } = this.props;
const linkClass = ["/add", "/create"].includes(pathname)
? "nav-link-active"
: "nav-link";
...
<div
id="createLink"
className={linkClass}
onClick={() => this.handleModalToggle()}
>
CREATE
</div>
...
}
There are two event listeners that are apparently useful when to monitor the network status:
1. window.addEventListener('online', console.log('Online'));
2. window.addEventListener('offline', console.log('Offline'));
But I am not sure where to register and use them. When I use them within componentDidMount, there is no use because monitoring would happen only if the component is mounted. I want to monitor the network status in one place and use it across the application. For this, dispatching the network status in redux would be more helpful. But the problem is where to listen for this events.
Simplified example with class components:
// In your main App component
componentDidMount() {
window.addEventListener('online', () => this.props.setConnectivity('online'));
window.addEventListener('offline', () => this.props.setConnectivity('offline'));
// You don't need to worry about removeEventlistener if your main App component never unmounts.
}
// Action
const setConnectivity = (status) => ({
type: 'SET_CONNECTIVITY',
payload: status === 'online'
})
// Reducer
const connectivityReducer = (state = initialState, action) => {
switch (action.type) {
case 'SET_CONNECTIVITY':
return {
...state,
isOnline: action.payload
};
}
};
// To let a component know about the connectivity status, simply use the flag from state:
const mapStateToProps = (state) => ({
isOnline: state.connectivity.isOnline
});
// To react to status changes in any other component:
componentDidUpdate(prevProps) {
const { isOnline } = this.props;
if (!prevProps.isOnline && isOnline) {
// We went online
} else if (prevProp.isOnline && !isOnline) {
// We went offline
}
}
I suggest to use this very new library
Then you can use useNetworkStatus to get what you wanted, something like:
import React from 'react';
import { useNetworkStatus } from 'react-adaptive-hooks/network';
const MyComponent = () => {
const { effectiveConnectionType } = useNetworkStatus();
let media;
switch(effectiveConnectionType) {
case 'slow-2g':
media = <img src='...' alt='low resolution' />;
break;
case '2g':
media = <img src='...' alt='medium resolution' />;
break;
case '3g':
media = <img src='...' alt='high resolution' />;
break;
case '4g':
media = <video muted controls>...</video>;
break;
default:
media = <video muted controls>...</video>;
break;
}
return <div>{media}</div>;
};
effectiveConnectionType - will change if the network changes, `
navigator.connection.addEventListener('change', func) - this will also get trigger on online and on offline
Here is a working example of an online / offline status indicator in React+Redux.
The magic comes from having a component that adds an event listener for the online event in the component's componentDidMount event. Then it dispatches an action via Redux to update the online status in the store. A container component (MainContainer in this example) can map the global store state to props, and any presentational component can update in response to this property changing.
By the way, if you want to test this online indicator on this page with Chrome: run the snippet, then launch the Dev Tools (F12), then toggle the device toolbar (Ctrl+Shift+M) (or 2nd icon in the top left of the dev tools window), switch to Responsive layout, and toggle the Online / Offline state in the bar (as shown in this picture) as desired:
function rootReducer(currentState, action) {
currentState = currentState || { status: true }; // Initial State
switch (action.type) {
case 'SET_ONLINE_STATUS':
return { ...currentState, status: action.status };
default:
return currentState; // Always return the state
}
}
// Action Creators:
function setOnlineStatus(status) {
return { type: 'SET_ONLINE_STATUS', status };
}
// Create Store
var rootStore = Redux.createStore(rootReducer);
// Map state and dispatch to props
function mapStateToProps(state) {
return {
status: state.status
};
}
function mapDispatchToProps(dispatch) {
return Redux.bindActionCreators({
setOnlineStatus: setOnlineStatus
}, dispatch);
}
// Connection indicator pure functional presentational component
var ConnectionIndicator = (props) => {
return (<div>You are: {props.status ? 'online' : 'offline'}</div>);
};
var Main = React.createClass({
render: function () {
return (<div>
<ConnectionIndicator status={this.props.status} />
</div>);
}
});
var OnlineWatcher = React.createClass({
render() { return null; },
componentDidMount() {
window.addEventListener('online', () => {
this.props.setOnlineStatus(true);
})
window.addEventListener('offline', () => {
this.props.setOnlineStatus(false);
})
}
});
// Container components (Pass props into presentational component)
var MainContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(Main);
var OnlineWatcherContainer = ReactRedux.connect(mapStateToProps,
mapDispatchToProps)(OnlineWatcher);
// Top-Level Component
var App = React.createClass({
render: function () {
return (
<div>
<MainContainer />
<OnlineWatcherContainer />
</div>
);
}
});
// Render to DOM
var Provider = ReactRedux.Provider; // Injects store into context of all descendents
ReactDOM.render(
<Provider store={rootStore}>
<App />
</Provider>,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.5.2/redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.5/react-redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.13.0/polyfill.js"></script>
<div id="container">
<!-- Yield to React -->
</div>
NOTE: In production code, you should also unsubscribe from the online and offline events in the componentWillUnmount function.
I used this fiddle as a starting point.
Before all, you should have redux store with actions and reducers.You are able to connect every component you want to store and use this source of truth. You can find documentations and exampel here https://react-redux.js.org/introduction/quick-start
Actually you can find example of using online status Integrating Navigator.onLine into React-Redux
when using hooks, this simple solution would work https://github.com/saulpalv/useOnline
npm i #saulpalv/useonline
use like this
import type { FC } from 'react';
import { useOnline } from '#saulpalv/useonline';
export const DialogNetwork: FC = ()=> {
const isOnline = useOnline();
return (
<Dialog show={!isOnline}>
You are not online
</Dialog >
);
});
I'm working on a Notification feature in my app (pretty much like Facebook notifications).
When I click a button in the header navigation, the dropdown opens and shows the notification list. The notification has a Link (from react-router) in it.
What I need to do is to close the dropdown whenever a Link is clicked.
Here's roughly the hierarchy I currently have:
Header > Navigation > Button > Dropdown > List > Notification > Link
Since the dropdown functionality is used more that once, I've abstracted its behavior away into a HOC that uses render prop:
export default function withDropDown(ClickableElement) {
return class ClickableDropdown extends PureComponent {
static propTypes = {
children: PropTypes.func.isRequired,
showOnInit: PropTypes.bool,
};
static defaultProps = {
showOnInit: false,
};
state = {
show: !!this.props.showOnInit,
};
domRef = createRef();
componentDidMount() {
document.addEventListener('mousedown', this.handleGlobalClick);
}
toggle = show => {
this.setState({ show });
};
handleClick = () => this.toggle(true);
handleGlobalClick = event => {
if (this.domRef.current && !this.domRef.current.contains(event.target)) {
this.toggle(false);
}
};
render() {
const { children, ...props } = this.props;
return (
<Fragment>
<ClickableElement {...props} onClick={this.handleClick} />
{this.state.show && children(this.domRef)}
</Fragment>
);
}
};
}
The HOC above encloses the Button component, so I have:
const ButtonWithDropdown = withDropdown(Button);
class NotificationsHeaderDropdown extends PureComponent {
static propTypes = {
data: PropTypes.arrayOf(notification),
load: PropTypes.func,
};
static defaultProps = {
data: [],
load: () => {},
};
componentDidMount() {
this.props.load();
}
renderDropdown = ref => (
<Dropdown ref={ref}>
{data.length > 0 && <List items={this.props.data} />}
{data.length === 0 && <EmptyList />}
</Dropdown>
);
render() {
return (
<ButtonWithDropdown count={this.props.data.length}>
{this.renderDropdown}
</ButtonWithDropdown>
);
}
}
List and Notification are both dumb functional components, so I'm not posting their code here. Dropdown is pretty much the same, with the difference it uses ref forwarding.
What I really need is to call that .toggle() method from ClickableDropdown created by the HOC to be called whenever I click on a Link on the list.
Is there any way of doing this without passing that .toggle() method down the Button > Dropdown > List > Notification > Link subtree?
I'm using redux, but I'm not sure this is the kind of thing I'd put on the store.
Or should I handle this imperatively using the DOM API, by changing the implementation of handleGlobalClick from ClickableDropdown?
Edit:
I'm trying with the imperative approach, so I've changed the handleGlobalClick method:
const DISMISS_KEY = 'dropdown';
function contains(current, element) {
if (!current) {
return false;
}
return current.contains(element);
}
function isDismisser(dismissKey, current, element) {
if (!element || !contains(current, element)) {
return false;
}
const shouldDismiss = element.dataset.dismiss === dismissKey;
return shouldDismiss || isDismisser(dismissKey, current, element.parentNode);
}
// Then...
handleGlobalClick = event => {
const containsEventTarget = contains(this.domRef.current, event.target);
const shouldDismiss = isDismisser(
DISMISS_KEY,
this.domRef.current,
event.target
);
if (!containsEventTarget || shouldDismiss) {
this.toggle(false);
}
return true;
};
Then I changed the Link to include a data-dismiss property:
<Link
to={url}
data-dismiss="dropdown"
>
...
</Link>
Now the dropdown is closed, but I'm not redirected to the provided url anymore.
I tried to defer the execution of this.toggle(false) using requestAnimationFrame and setTimeout, but it didn't work either.
Solution:
Based on the answer by #streletss bellow, I came up with the following solution:
In order to be as generic as possible, I created a shouldHideOnUpdate prop in the ClickableDropdown dropdown component, whose Hindley-Milner-ish signature is:
shouldHideOnUpdate :: Props curr, Props prev => (curr, prev) -> Boolean
Here's the componentDidUpdate implementation:
componentDidUpdate(prevProps) {
if (this.props.shouldHideOnUpdate(this.props, prevProps)) {
this.toggle(false);
}
}
This way, I didn't need to use the withRouter HOC directly in my withDropdown HOC.
So, I lifted the responsibility of defining the condition for hiding the dropdown to the caller, which is my case is the Navigation component, where I did something like this:
const container = compose(withRouter, withDropdown);
const ButtonWithDropdown = container(Button);
function routeStateHasChanged(currentProps, prevProps) {
return currentProps.location.state !== prevProps.location.state;
}
// ... then
render() {
<ButtonWithDropdown shouldHideOnUpdate={routeStateHasChanged}>
{this.renderDropdown}
</ButtonWithDropdown>
}
It seems you could simply make use of withRouter HOC and check if this.props.location.pathname has changed when componentDidUpdate:
export default function withDropDown(ClickableElement) {
class ClickableDropdown extends Component {
// ...
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
this.toggle(false);
}
}
// ...
};
return withRouter(ClickableDropdown)
}
Is there any way of doing this without passing that .toggle() method down the Button > Dropdown > List > Notification > Link subtree?
In the question, you mention that you are using redux.So I assume that you store showOnInit in redux.We don't usually store a function in redux.In toggle function,I think you should dispatch an CHANGE_SHOW action to change the showOnInit in redux, then pass the show data not the function to the children component.Then after reducer dispatch,the react will change “show” automatically.
switch (action.type) {
case CHANGE_SHOW:
return Object.assign({}, state, {
showOnInit: action.text
})
...
default:
return state
}
Link element and data pass
Use the property in Link-to,not data-...Like this:
<Link
to={{
pathname: url,
state:{dismiss:"dropdown"}
}}
/>
And the state property will be found in this.props.location.
give context a little try(not recommend)
It may lead your project to instable and some other problems.(https://reactjs.org/docs/context.html#classcontexttype)
First,define context
const MyContext = React.createContext(defaultValue);
Second,define pass value
<MyContext.Provider value={this.toggle}>
Then,get the value in the nested component
<div value={this.context} />
Let us assume we have a statefull React component (configured to work with Redux):
export class SomeComponent extends Component {
state = {
someObject: {}
};
componentWillMount() {
this.props.getNews();
this.props.getFakeNews();
}
render() {
const {
news,
fakeNews
} = this.props;
if(_.isEmpty(news) || _.isEmpty(fakeNews)){
return <div>Loading</div>
}else{
return <div>Here all component stuff</div>
}
}
SomeComponent.propTypes = {
news: PropTypes.array.isRequired,
fakeNews: PropTypes.array.isRequired
};
export const Some = connect(
state => ({
news: newsSelectors.list(state),
fakeNews: fakeNewsSelectors.list(state)
}),
{
getNews,
getFakeNEws
}
)(withStyles(styles)(SomeComponent), withRouter(SomeComponent));
This component will re-render two times during getting news and fake news. In the render method we need to check if both of them are loaded.
Is there any way to trigger render only when all props are loaded?
In a perfect scenario I'd like to have no detailed null/empty check on the set of props. I believe React or Redux should perform this operation on its own as long the prop is configured as required.
You can add a lifecycle method `shouldComponentUpdate(nextProps, nextState).
You can add the following method and it should resolve it for you:
shouldComponentUpdate(nextProps, nextState) {
if (_.isEmpty(nextProps.news) || _.isEmpty(nextProps.fakeNews)) {
return false;
}
return true;
}
You could do something like:
// HOC factory
function ifComponent (predicate, PlaceHolder) {
return Component => class If extends React.Component {
render () {
if (predicate(this.props)) {
return <Component {...this.props} />
}
return <PlaceHolder {...this.props} />
}
}
}
}
// create the customHOC
const whenPropsLoaded = ifComponent(props => props.news && props.fakeNews, Loader);
// compose the two HOCs using the `compose` function in redux (simple function composition)
const News = compose(
connect(getNewsProps),
whenPropsLoaded(DisplayNews)
);
As a side note you may be interested in the recompose utility library bad its branch HOC (docs here). I think this is pretty much what you want as you seem to know about HOCs.
If you want to avoid null and undefined values from redux. You can use Selectors it was very easy to avoid those things.
const newsSelectors = (state) => {
if(!state.list) { *//list is null or undefined*
return [] or {} *//your wish (Proptypes required value)*
}
else {
return state.list
}
}
export { newsSelectors };
I think you can solve the issue if you rewrite the render function as below.
render() {
const {
news,
fakeNews
} = this.props;
return (
{news && fakeNews ?
<div>Here all component stuff</div>
: <div>Loading</div> }
)
}
I hope this helps you.
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)
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();
}
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, ])
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
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 */;
}
});
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.
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);
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]);
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
}
}
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
})
}, [])
For giving warning on the press of browser back in react functional components. do the following steps
declare isBackButtonClicked and initialize it as false and maintain the state using setBackbuttonPress function.
const [isBackButtonClicked, setBackbuttonPress] = useState(false);
In componentdidmount, add the following lines of code
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
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;
}}
/>
just put in componentDidMount()
componentDidMount() {
window.onbeforeunload =this.beforeUnloadListener;
}
beforeUnloadListener = (event) => {
event.preventDefault();
return event.returnValue = "Are you sure you want to exit?";
};
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`
);
});
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.
Upcoming version 6.0 introduces useBlocker hook - which could be used to intercept all navigation attempts.
import { Action } from 'history';
import { useBlocker } from 'react-router';
// when blocker should be active
const unsavedChanges = true;
useBlocker((transition) => {
const {
location, // The new location
action, // The action that triggered the change
} = transition;
// intercept back and forward actions:
if (action === Action.Pop) {
alert('intercepted!')
}
}, unsavedChanges);
You can use "withrouter" HOC and use this.props.history.goBack.
<Button onClick={this.props.history.goBack}>
BACK
</Button>