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

Feature/next location as props #1

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ 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.10.1] - 2023-01-10
### Changed
- Added nextLocation and action to childData
- Added callbacks which will be called on become active: onShow() and onShowNative()
- Added method to navigate to another route during the prompt is active

## [1.9.6] - 2022-02-13
### Fixed
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,17 @@ import Modal from "./your-own-code";
- `props`
- afterCancel?: Function,
- afterConfirm?: Function,
- allowGoBack: bool (use _goBack_ method instead of _push_ when navigating back -- !! NOTE WELL !! it will _always_ navigate back only 1 item, even when it should navigate back more items. read more: https://github.com/ZacharyRSmith/react-router-navigation-prompt/pull/30),
- afterSkip?: Function,
- allowGoBack?: bool (use _goBack_ method instead of _push_ when navigating back -- !! NOTE WELL !! it will _always_ navigate back only 1 item, even when it should navigate back more items. read more: https://github.com/ZacharyRSmith/react-router-navigation-prompt/pull/30),
- beforeCancel?: Function,
- beforeConfirm?: Function,
- children: (data: {isActive: bool, onCancel: Function, onConfirm: Function}) => React$Element<\*>,
- renderIfNotActive: bool,
- beforeSkip?: Function,
- onShow?: (data: {action: ?HistoryAction, nextLocation: ?Location, onCancel: Function, onConfirm: Function, onSkip: (nextLocation: Location | string, action?: HistoryAction) => void}) => void,
- onShowNative?: Function,
- children: (data: {isActive: bool, action: ?HistoryAction, nextLocation: ?Location, onCancel: Function, onConfirm: Function, onSkip: (nextLocation: Location | string, action?: HistoryAction) => void}) => React$Element<\*>,
- renderIfNotActive?: bool,
- when: bool | (Location, ?Location, ?HistoryAction) => bool,
- disableNative: bool,
- disableNative?: bool,
// Added by react-router:
- match: Match,
- history: RouterHistory,
Expand Down
316 changes: 1 addition & 315 deletions es/index.js

Large diffs are not rendered by default.

82 changes: 76 additions & 6 deletions es/index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ declare type PropsT = {
afterConfirm?: Function,
beforeCancel?: Function,
beforeConfirm?: Function,
children: (data: {isActive: bool, onCancel: Function, onConfirm: Function}) => React$Element<*>,
beforeSkip?: Function,
afterSkip?: Function,
onShow?: (data: {action: ?HistoryAction, nextLocation: ?Location, onCancel: Function, onConfirm: Function, onSkip: (nextLocation: Location | string | URL, action?: HistoryAction) => void}) => void,
onShowNative?: Function,
children: (data: {isActive: bool, action: ?HistoryAction, nextLocation: ?Location, onCancel: Function, onConfirm: Function, onSkip: (nextLocation: Location | string | URL, action?: HistoryAction) => void}) => React$Element<*>,
match: Match,
history: RouterHistory,
location: Location,
renderIfNotActive?: bool,
when: bool | (Location, ?Location, ?HistoryAction) => bool,
disableNative?: bool,
allowGoBack?: bool,
allowGoBack?: bool
};
declare type StateT = {
action: ?HistoryAction,
Expand Down Expand Up @@ -67,6 +71,7 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
(this:Object).onBeforeUnload = this.onBeforeUnload.bind(this);
(this:Object).onCancel = this.onCancel.bind(this);
(this:Object).onConfirm = this.onConfirm.bind(this);
(this:Object).onSkip = this.onSkip.bind(this);
(this:Object).when = this.when.bind(this);

this.state = {...initState, unblock: () => {}/* unblock will be set in componentDidMount */};
Expand All @@ -85,6 +90,8 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
this.props.afterCancel();
} else if (this._prevUserAction === 'CONFIRM' && typeof this.props.afterConfirm === 'function') {
this.props.afterConfirm();
} else if (this._prevUserAction === 'SKIP' && typeof this.props.afterSkip === 'function') {
this.props.afterSkip();
}
this._prevUserAction = '';
}
Expand All @@ -108,12 +115,20 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
action,
nextLocation,
isActive: true
});
}, () => this.props.onShow &&
this.props.onShow({
action: this.state.action,
nextLocation: this.state.nextLocation,
onConfirm: this.onConfirm,
onCancel: this.onCancel,
onSkip: this.onSkip
})
);
}
return !result;
}

navigateToNextLocation(cb) {
navigateToNextLocation() {
let {action, nextLocation} = this.state;
action = {
'POP': this.props.allowGoBack ? 'goBack' : 'push',
Expand Down Expand Up @@ -152,6 +167,57 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
}
}

navigateTo(nextLocation, action) {
const method = {
'POP': '',
'PUSH': 'push',
'REPLACE': 'replace'
}[action || 'PUSH'];
if (!method)
throw new Error('Action is not supported!');

const {history} = this.props;

this.state.unblock();
this._prevUserAction = 'SKIP';
// $FlowFixMe history.replace()'s type expects LocationShape even though it works with Location.
history[method](nextLocation); // could unmount at this point
if (this._isMounted) { // Just in case we unmounted on the route change
this.setState({
...initState,
unblock: this.props.history.block(this.block)
}); // FIXME? Does history.listen need to be used instead, for async?
}
}

navigateToNative(nextLocation, action) {
if (!this.props.disableNative) {
window.removeEventListener('beforeunload', this.onBeforeUnload);
}

if (action === 'PUSH') {
window.location.assign(nextLocation);
} else if (action === 'REPLACE') {
window.location.replace(nextLocation);
} else {
throw new Error('Action is not supported!');
}
}

onSkip(nextLocation, action) {
(this.props.beforeSkip || ((cb) => {
cb();
}))(() => {
if (nextLocation instanceof URL) {
this.navigateToNative(nextLocation.toString(), action);
} else if (typeof nextLocation === 'string' && /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/.test(nextLocation)) {
this.navigateToNative(nextLocation, action);
} else {
this.navigateTo(nextLocation, action);
}
});
}

onCancel() {
(this.props.beforeCancel || ((cb) => {
cb();
Expand All @@ -165,12 +231,13 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
(this.props.beforeConfirm || ((cb) => {
cb();
}))(() => {
this.navigateToNextLocation(this.props.afterConfirm);
this.navigateToNextLocation();
});
}

onBeforeUnload(e) {
if (!this.when()) return;
this.props.onShowNative && this.props.onShowNative();
const msg = 'Do you want to leave this site?\n\nChanges you made may not be saved.';
e.returnValue = msg;
return msg;
Expand All @@ -190,8 +257,11 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
<div>
{this.props.children({
isActive: this.state.isActive,
action: this.state.action,
nextLocation: this.state.nextLocation,
onConfirm: this.onConfirm,
onCancel: this.onCancel
onCancel: this.onCancel,
onSkip: this.onSkip
})}
</div>
);
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-router-navigation-prompt",
"version": "1.9.6",
"name": "react-router-navigation-prompt-flow",
"version": "1.10.3",
"description": "A replacement component for the react-router `<Prompt/>`. Allows for more flexible dialogs.",
"scripts": {
"build": "webpack",
Expand All @@ -15,7 +15,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/ZacharyRSmith/react-router-navigation-prompt.git"
"url": "git+https://github.com/VOMATEC-Innovations/react-router-navigation-prompt.git"
},
"keywords": [
"confirm",
Expand All @@ -26,6 +26,7 @@
"user"
],
"author": "@ZacharyRSmith",
"contributors": ["Fabian Wassenhoven <[email protected]>"],
"license": "MIT",
"bugs": {
"url": "https://github.com/ZacharyRSmith/react-router-navigation-prompt/issues"
Expand Down Expand Up @@ -66,6 +67,7 @@
"testcafe": "^1.18.3",
"typescript": "^3.1.1",
"webpack": "^5.68.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.7.4"
}
}
82 changes: 76 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ declare type PropsT = {
afterConfirm?: Function,
beforeCancel?: Function,
beforeConfirm?: Function,
children: (data: {isActive: bool, onCancel: Function, onConfirm: Function}) => React$Element<*>,
beforeSkip?: Function,
afterSkip?: Function,
onShow?: (data: {action: ?HistoryAction, nextLocation: ?Location, onCancel: Function, onConfirm: Function, onSkip: (nextLocation: Location | string | URL, action?: HistoryAction) => void}) => void,
onShowNative?: Function,
children: (data: {isActive: bool, action: ?HistoryAction, nextLocation: ?Location, onCancel: Function, onConfirm: Function, onSkip: (nextLocation: Location | string | URL, action?: HistoryAction) => void}) => React$Element<*>,
match: Match,
history: RouterHistory,
location: Location,
renderIfNotActive?: bool,
when: bool | (Location, ?Location, ?HistoryAction) => bool,
disableNative?: bool,
allowGoBack?: bool,
allowGoBack?: bool
};
declare type StateT = {
action: ?HistoryAction,
Expand Down Expand Up @@ -67,6 +71,7 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
(this:Object).onBeforeUnload = this.onBeforeUnload.bind(this);
(this:Object).onCancel = this.onCancel.bind(this);
(this:Object).onConfirm = this.onConfirm.bind(this);
(this:Object).onSkip = this.onSkip.bind(this);
(this:Object).when = this.when.bind(this);

this.state = {...initState, unblock: () => {}/* unblock will be set in componentDidMount */};
Expand All @@ -85,6 +90,8 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
this.props.afterCancel();
} else if (this._prevUserAction === 'CONFIRM' && typeof this.props.afterConfirm === 'function') {
this.props.afterConfirm();
} else if (this._prevUserAction === 'SKIP' && typeof this.props.afterSkip === 'function') {
this.props.afterSkip();
}
this._prevUserAction = '';
}
Expand All @@ -108,12 +115,20 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
action,
nextLocation,
isActive: true
});
}, () => this.props.onShow &&
this.props.onShow({
action: this.state.action,
nextLocation: this.state.nextLocation,
onConfirm: this.onConfirm,
onCancel: this.onCancel,
onSkip: this.onSkip
})
);
}
return !result;
}

navigateToNextLocation(cb) {
navigateToNextLocation() {
let {action, nextLocation} = this.state;
action = {
'POP': this.props.allowGoBack ? 'goBack' : 'push',
Expand Down Expand Up @@ -152,6 +167,57 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
}
}

navigateTo(nextLocation, action) {
const method = {
'POP': '',
'PUSH': 'push',
'REPLACE': 'replace'
}[action || 'PUSH'];
if (!method)
throw new Error('Action is not supported!');

const {history} = this.props;

this.state.unblock();
this._prevUserAction = 'SKIP';
// $FlowFixMe history.replace()'s type expects LocationShape even though it works with Location.
history[method](nextLocation); // could unmount at this point
if (this._isMounted) { // Just in case we unmounted on the route change
this.setState({
...initState,
unblock: this.props.history.block(this.block)
}); // FIXME? Does history.listen need to be used instead, for async?
}
}

navigateToNative(nextLocation, action) {
if (!this.props.disableNative) {
window.removeEventListener('beforeunload', this.onBeforeUnload);
}

if (action === 'PUSH') {
window.location.assign(nextLocation);
} else if (action === 'REPLACE') {
window.location.replace(nextLocation);
} else {
throw new Error('Action is not supported!');
}
}

onSkip(nextLocation, action) {
(this.props.beforeSkip || ((cb) => {
cb();
}))(() => {
if (nextLocation instanceof URL) {
this.navigateToNative(nextLocation.toString(), action);
} else if (typeof nextLocation === 'string' && /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/.test(nextLocation)) {
this.navigateToNative(nextLocation, action);
} else {
this.navigateTo(nextLocation, action);
}
});
}

onCancel() {
(this.props.beforeCancel || ((cb) => {
cb();
Expand All @@ -165,12 +231,13 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
(this.props.beforeConfirm || ((cb) => {
cb();
}))(() => {
this.navigateToNextLocation(this.props.afterConfirm);
this.navigateToNextLocation();
});
}

onBeforeUnload(e) {
if (!this.when()) return;
this.props.onShowNative && this.props.onShowNative();
const msg = 'Do you want to leave this site?\n\nChanges you made may not be saved.';
e.returnValue = msg;
return msg;
Expand All @@ -190,8 +257,11 @@ class NavigationPrompt extends React.Component<PropsT, StateT> {
<div>
{this.props.children({
isActive: this.state.isActive,
action: this.state.action,
nextLocation: this.state.nextLocation,
onConfirm: this.onConfirm,
onCancel: this.onCancel
onCancel: this.onCancel,
onSkip: this.onSkip
})}
</div>
);
Expand Down
Loading