Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for #2 (confirmation on browsers back button) #30

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [1.7.0] - 2018-09-26
### Added
- Typescript typings

## [1.6.6] - 2018-09-15
### Added
- Note in README.md that BrowserHistory is supported, but not HashHistory
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import Modal from "./your-own-code";
- renderIfNotActive: bool,
- when: bool | (Location, ?Location) => bool,
- disableNative: bool,
- allowGoBack: bool (use _goBack_ method instead of _push_ when navigating back one or more items),
// Added by react-router:
- match: Match,
- history: RouterHistory,
Expand Down
31 changes: 25 additions & 6 deletions es/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,14 @@ var NavigationPrompt = function (_React$Component) {
}, {
key: 'navigateToNextLocation',
value: function navigateToNextLocation(cb) {
var _this2 = this;

var _state = this.state,
action = _state.action,
nextLocation = _state.nextLocation;

action = {
'POP': 'push',
'POP': this.props.allowGoBack ? 'goBack' : 'push',
'PUSH': 'push',
'REPLACE': 'replace'
}[action || 'PUSH'];
Expand All @@ -207,6 +209,23 @@ var NavigationPrompt = function (_React$Component) {


this.state.unblock();

// Special handling for goBack
if (action === 'goBack') {
history.goBack();
this._prevUserAction = 'CONFIRM';
// As native history.go(-1) exetues after this method has finished, need to update state asychronously
// otherwise it will trigger navigateToNextLocation method again
return window.setTimeout(function () {
// Skip state update when component has been unmounted in meanwhile. Usually this is what happens.
if (_this2._isMounted) {
_this2.setState(_extends({}, initState, {
unblock: _this2.props.history.block(_this2.block)
}));
}
}, 25);
}

// $FlowFixMe history.replace()'s type expects LocationShape even though it works with Location.
history[action](nextLocation); // could unmount at this point
this._prevUserAction = 'CONFIRM';
Expand All @@ -220,24 +239,24 @@ var NavigationPrompt = function (_React$Component) {
}, {
key: 'onCancel',
value: function onCancel() {
var _this2 = this;
var _this3 = this;

(this.props.beforeCancel || function (cb) {
cb();
})(function () {
_this2._prevUserAction = 'CANCEL';
_this2.setState(_extends({}, initState));
_this3._prevUserAction = 'CANCEL';
_this3.setState(_extends({}, initState));
});
}
}, {
key: 'onConfirm',
value: function onConfirm() {
var _this3 = this;
var _this4 = this;

(this.props.beforeConfirm || function (cb) {
cb();
})(function () {
_this3.navigateToNextLocation(_this3.props.afterConfirm);
_this4.navigateToNextLocation(_this4.props.afterConfirm);
});
}
}, {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-router-navigation-prompt",
"version": "1.6.6",
"version": "1.7.0",
"description": "A replacement component for the react-router `<Prompt/>`. Allows for more flexible dialogs.",
"scripts": {
"build": "webpack",
Expand Down Expand Up @@ -32,6 +32,7 @@
"jsnext:main": "es/index.js",
"main": "es/index.js",
"module": "es/index.js",
"typings": "types/index.d.ts",
"homepage": "https://github.com/ZacharyRSmith/react-router-navigation-prompt#readme",
"peerDependencies": {
"react": ">= 15",
Expand Down
21 changes: 20 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ declare type PropsT = {
renderIfNotActive: bool,
when: bool | (Location, ?Location) => bool,
disableNative: bool,
allowGoBack: bool,
};
declare type StateT = {
action: ?HistoryAction,
Expand Down Expand Up @@ -113,14 +114,32 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
navigateToNextLocation(cb) {
let {action, nextLocation} = this.state;
action = {
'POP': 'push',
'POP': this.props.allowGoBack ? 'goBack' : 'push',
'PUSH': 'push',
'REPLACE': 'replace'
}[action || 'PUSH'];
if (!nextLocation) nextLocation = {pathname: '/'};
const {history} = this.props;

this.state.unblock();

// Special handling for goBack
if (action === 'goBack') {
history.goBack();
this._prevUserAction = 'CONFIRM';
// As native history.go(-1) exetues after this method has finished, need to update state asychronously
// otherwise it will trigger navigateToNextLocation method again
return window.setTimeout(() => {
// Skip state update when component has been unmounted in meanwhile. Usually this is what happens.
if (this._isMounted) {
this.setState({
...initState,
unblock: this.props.history.block(this.block)
});
}
}, 25);
}

// $FlowFixMe history.replace()'s type expects LocationShape even though it works with Location.
history[action](nextLocation); // could unmount at this point
this._prevUserAction = 'CONFIRM';
Expand Down
44 changes: 44 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as React from 'react';
import * as H from 'history';
import { RouteComponentProps, Omit } from 'react-router';

declare module 'react-router-navigation-prompt' {
export interface ChildData {
isActive: boolean;
onCancel: () => void;
onConfirm: () => void;
}

export interface NavigationPromptProps extends RouteComponentProps<any> {
children: (data: ChildData) => React.ReactNode;
when: boolean | ((currentLocation: H.Location, nextLocation?: H.Location) => boolean);
afterCancel?: () => void;
afterConfirm?: () => void;
beforeCancel?: () => void;
beforeConfirm?: () => void;
renderIfNotActive?: boolean;
disableNative?: boolean;
}

interface NavigationPromptState {
action?: H.Action;
nextLocation?: H.Location;
isActive: boolean;
unblock: () => void;
}

export class NavigationPrompt extends React.Component<NavigationPromptProps, NavigationPromptState> {
_prevUserAction: string;
_isMounted: boolean;

block(nextLocation: H.Location, action: H.Action): boolean;
navigateToNextLocation(cb: () => void): void;
onCancel(): void;
onConfirm(): void;
onBeforeUnload(e: any): string
when(nextLocation?: H.Location): boolean;
}
}

// This is for the withRouter HOC being used as the default export.
export default function NavigationPrompt(): React.Component<Omit<NavigationPromptProps, keyof RouteComponentProps<any>>>;