Detecting user leaves page with custom popup in reactjs - reactjs

I want to open a popup to warn the user that their data would be lost if they leave the form without submitting it.
I've heard about Prompt can do it, but they didn't support I customize the Popup, button, or handling confirmation button or cancel.
is there any way or component that can help me to show the popup prevent the user leaves the form with 2 customize buttons.
Thank you in advance.

use react-confirm-alert
install:
npm i react-confirm-alert
using:
import { confirmAlert } from 'react-confirm-alert';
import 'styles/react-confirm-alert.css';
create this customize function
const MyCustomConfirm = (content, onAccept, title, acceptTxt, rejectTxt) => {
acceptTxt = acceptTxt || "Yes";
rejectTxt = rejectTxt || "No";
title = title || "Message";
confirmAlert({
title: title,
message: <div className="react-confirm-alert-message">{content}</div>,
buttons: [
{
label: acceptTxt,
className: "btn-secondary", // your own css
onClick: () => {
if (onAccept) {
onAccept();
}
}
},
{
label: rejectTxt,
className: "btn-secondary", // your own css
onClick: () => { }
}
]
});
}
now you can call it anywhere. example :
MyCustomConfirm(' Do you want to clear all ? ', function () {
searchInput.val("");
$(this).val("");
});

use onBlur event on your parent element of form
myFunction = () =>{
MyCustomConfirm(' Do you want to clear all ? ', function () {
searchInput.val("");
$(this).val("");
});
return false;
}
render(){
// your parent element of form
<form onBlur={this.myFunction()>
</form>
}
or you can call this popup when user try to close your form.
or even the document level you can use this :
window.addEventListener("beforeunload", function (e) {
if (formSubmitting) {
return undefined;
}

Related

React confirm alert handle key press = enter

import { confirmAlert } from "react-confirm-alert"; // Import
import "react-confirm-alert/src/react-confirm-alert.css"; // Import css
export default function App() {
const handleButtonPress = (event) => {
if (event.key === "Enter") {
alert("Click Yes");
}
};
const handleClick = (e) => {
confirmAlert({
title: "Confirm to submit",
message: "Are you sure to do this.",
buttons: [
{
label: "Yes",
onClick: () => {
alert("Click Yes");
},
onKeyPress: () => {
handleButtonPress();
}
},
{
label: "No",
onClick: () => {
alert("Click No");
}
}
]
});
};
return (
<div className="App">
<button
onClick={() => {
handleClick();
}}
>
Click
</button>
</div>
);
}
I'm testing react-confirm-alert.
I'm trying to handle Button Yes by pressing Enter. Both function onClick() for yes and no are working good, but press enter is not working.
Can someone let me know if I did something wrong?
There's several issues. This react-confirm-alert library looks like a poor library, I'd pick a different one if you can find one that suits your needs.
You're calling handleButtonPress() with no arguments, and then you're trying to read from the event object, which you don't pass.
const handleButtonPress = (event) => {
if (event.key === "Enter") {
The event.key line should be throwing an error since event is undefined. Since you're not seeing errors, it's clear this line isn't getting called. You should be using console.log or the debugger to double check what code is getting called.
You should also get in the habit of reading documentation. In this case, the documentation shows that onKeyPress is a top level setting, while you've incorrectly put it in buttons.
Either way, react-confirm-alert doesn't pass the event to the onKeyPress callback: https://github.com/GA-MO/react-confirm-alert/#options so it doesn't seem like this API should exist. It doesn't have any use.
I would either pick a different library, otherwise you'll need to add your own keypress listener to the document, and manually handle the enter key there.

autosuggest not showing item immediately

I am looking into fixing a bug in the code. There is a form with many form fields. Project Name is one of them. There is a button next to it.So when a user clicks on the button (plus icon), a popup window shows up, user enters Project Name and Description and hits submit button to save the project.
The form has Submit, Reset and Cancel button (not shown in the code for breviety purpose).
The project name field of the form has auto suggest feature. The code snippet below shows the part of the form for Project Name field.So when a user starts typing, it shows the list of projects
and user can select from the list.
<div id="formDiv">
<Growl ref={growl}/>
<Form className="form-column-3">
<div className="form-field project-name-field">
<label className="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated custom-label">Project Name</label>
<AutoProjects
fieldName='projectId'
value={values.projectId}
onChange={setFieldValue}
error={errors.projects}
touched={touched.projects}
/>{touched.projects && errors.v && <Message severity="error" text={errors.projects}/>}
<Button className="add-project-btn" title="Add Project" variant="contained" color="primary"
type="button" onClick={props.addProject}><i className="pi pi-plus" /></Button>
</div>
The problem I am facing is when some one creates a new project. Basically, the autosuggest list is not showing the newly added project immediately after adding/creating a new project. In order to see the newly added project
in the auto suggest list, after creating a new project,user would have to hit cancel button of the form and then open the same form again. In this way, they can see the list when they type ahead to search for the project they recently
created.
How should I make sure that the list gets immediately updated as soon as they have added the project?
Below is how my AutoProjects component looks like that has been used above:
import React, { Component } from 'react';
import Autosuggest from 'react-autosuggest';
import axios from "axios";
import { css } from "#emotion/core";
import ClockLoader from 'react-spinners/ClockLoader'
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// Use your imagination to render suggestions.
const renderSuggestion = suggestion => (
<div>
{suggestion.name}, {suggestion.firstName}
</div>
);
const override = css`
display: block;
margin: 0 auto;
border-color: red;
`;
export class AutoProjects extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
projects: [],
suggestions: [],
loading: false
}
this.getSuggestionValue = this.getSuggestionValue.bind(this)
this.setAutoSuggestValue = this.setAutoSuggestValue.bind(this)
}
// Teach Autosuggest how to calculate suggestions for any given input value.
getSuggestions = value => {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp(escapedValue, 'i');
const projectData = this.state.projects;
if (projectData) {
return projectData.filter(per => regex.test(per.name));
}
else {
return [];
}
};
// When suggestion is clicked, Autosuggest needs to populate the input
// based on the clicked suggestion. Teach Autosuggest how to calculate the
// input value for every given suggestion.
getSuggestionValue = suggestion => {
this.props.onChange(this.props.fieldName, suggestion.id)//Update the parent with the new institutionId
return suggestion.name;
}
fetchRecords() {
const loggedInUser = JSON.parse(sessionStorage.getItem("loggedInUser"));
return axios
.get("api/projects/search/getProjectSetByUserId?value="+loggedInUser.userId)//Get all personnel
.then(response => {
return response.data._embedded.projects
}).catch(err => console.log(err));
}
setAutoSuggestValue(response) {
let projects = response.filter(per => this.props.value === per.id)[0]
let projectName = '';
if (projects) {
projectName = projects.name
}
this.setState({ value: projectName})
}
componentDidMount() {
this.setState({ loading: true}, () => {
this.fetchRecords().then((response) => {
this.setState({ projects: response, loading: false }, () => this.setAutoSuggestValue(response))
}).catch(error => error)
})
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
// Autosuggest will call this function every time you need to update suggestions.
// You already implemented this logic above, so just use it.
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: this.getSuggestions(value)
});
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render() {
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: value,
value,
onChange: this.onChange
};
// Finally, render it!
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
<div className="sweet-loading">
<ClockLoader
css={override}
size={50}
color={"#123abc"}
loading={this.state.loading}
/>
</div>
</div>
);
}
}
The problem is you only call the fetchRecord when component AutoProjects did mount. That's why whenever you added a new project, the list didn't update. It's only updated when you close the form and open it again ( AutoProjects component mount again)
For this case I think you should lift the logic of fetchProjects to parent component and past the value to AutoProjects. Whenever you add new project you need to call the api again to get a new list.

Smart PayPal Buttons in React, disable the button

I have this implementation of the PayPal smart buttons in React:
function PayPalButtonComponent(props: PayPalButtonProps) {
const [show, set_show] = useState(false);
const [error, set_error] = useState<string>();
const create_order = (_: any, actions: any) => {
return actions.order.create({
purchase_units: [
{
amount: {
currency: props.currency || "EUR",
value: props.total
}
}
]
});
};
const handle_approve = (_: any, actions: any) => {
return actions.order.capture().then((details: any) => {
if (props.onSuccess) props.onSuccess(details);
});
};
const handle_cancel = () => {
if (props.onCancel) props.onCancel();
};
const handle_error = () => {
if (props.onError) props.onError();
};
const render_button = () => {
const Button = paypal.Buttons.driver("react", { React, ReactDOM });
return (
<Button
style={{
layout: "horizontal",
size: "responsive",
shape: "rect",
color: "gold",
tagline: false
}}
funding={{
allowed: [paypal.FUNDING.CARD, paypal.FUNDING.PAYPAL],
disallowed: [paypal.FUNDING.CREDIT]
}}
createOrder={create_order}
onApprove={handle_approve}
onError={handle_error}
onCancel={handle_cancel}
/>
);
};
useEffect(() => {
if (props.isScriptLoaded) {
if (props.isScriptLoadSucceed) set_show(true);
else set_error("Unable to load the paypalscript");
}
}, [props.isScriptLoaded, props.isScriptLoadSucceed]);
if (error) return <p>{error}</p>;
if (!show) return <FakeButton />;
return render_button();
}
I have struggled to implement these buttons in react since there is no documentation, I have found and copied some code from here and trying to guess other stuff. But I can't understand how to disable the button.
In this guide they state that one can call the disable() method on the actions object but can't figure out how I can accomplish that with my configuration.
Have you ever try something similar? Do you know any documentation one can follow?
edit
What I'm trying to accomplish is to set the button in a disable state during the payment. I know there is the paypal overlay, but when the transaction completes I change the app route and since it happen when onSuccess is called, due to the apparent async nature of actions.order.capture() this can't happen instantaneously, and so there is a moment when one can click again on the paypal button. If i can disable the button i have solve the problem.
The onInit implementation allows you to disable/enable the button before clicking on it, useful for some sort of validation before the checkout (like terms checked) but doesn't apply to my case. I have also tried to call actions.disable() in create_order but that breaks the button.
Have same situation here and the onInit example from documentation is not that straight forward as some think... You would need to create hidden imput and assign somsort of variable to it when payment made in order to achieve buttons to be disabled after payment. To bad no one solve this one.

React js - Ant Design switch changing although Ok button is not pressed

Hello I am having a problem here in my switch and by the way I am using Ant Design switch. What I want to happen is when I click the switch a popup should appear first asking if the user wants to switch on or off and when the user click ok the switch should change.
Here is my code
import Modal from 'antd/es/modal';
import Switch from 'antd/es/switch
const { confirm } = Modal;
handleOk = () => {
confirm ({
title: Are you sure you want to switch on ?
okText: 'Yes',
cancelText: 'No',
onOk() {
console.log('The switch is now on')
}
}
And here is my code for switch
<Switch onClick={this.handleOk} />
The output is when I click the switch the popup or modal appear and when I click the Ok button it logs in the console "The switch is now on". My only problem is the switch is moving although I didnt click the Ok button.
You need to make it as a controlled component to control weather it must be switched or not.
export function MyComponent = () => {
const [checked, setChecked] = useState(false)
const handleClick = () => {
if (!checked) {
confirm ({
title: Are you sure you want to switch on ?
okText: 'Yes',
cancelText: 'No',
onOk() {
setChecked(true)
}})
} else {
setChecked(false)
}
}
return (
<Switch onClick={handleClick} checked={checked}/>
)
}

Show custom dialog setRouteLeaveHook or history.listenBefore react-router/redux?

I cant seem to figure out how to show a custom dialogue instead of using the normal window.confirm that routeWillLeave and history.listenBefore uses. Basically i have built a notification system and check if a form is dirty const { dispatch, history, dirty } = this.props;
if the form is dirty it means the user has unsaved changes. If they change route I would like to show my notification which will have two buttons STAY, IGNORE which can both take an onClick handler.
Ive spent a bit of time googling and havent come across any mention of how i might accomplish this using routeWillLeave. The closest thing i could find was to use history.listenBefore however there docs say that I need to do this.
let history = createHistory({
getUserConfirmation(message, callback) {
callback(window.confirm(message)) // The default behavior
}
})
But I am using browserHistory from react-router to initiate my store const history = syncHistoryWithStore(browserHistory, store);
How can I stop a route change after a link has been clicked, show a notification using my custom notification system and depending on which button is clicked either transition to the new route or stay?
Here is an example of how my notification system works and the direction ive headed in which obviously doesn't work because all this returns is a string to show in the window.confirm dialogue by default.
history.listenBefore((location) => {
if(this.props.dirty){
const acceptBtn = {
title: 'STAY',
handler: ignoreRouteChange() //This can be any function I want
}
const denyBtn = {
title: 'IGNORE',
handler: continueRouteChange() //This can be any function I want
}
return dispatch(addNotification({
message: 'You have unsaved changes!',
type: NOTIFICATION_TYPE_WARNING,
duration: 0,
canDismiss: false,
acceptBtn,
denyBtn
}));
return "Usaved changes!"
}
});
Thanks
Another solution that i have decided to use is to return false in the normal setRouterLeaveHook callback and then show my dialog and use the nextLocation passed to the callback to push the next route depending on button selection.
router.setRouteLeaveHook(route, this.routerWillLeave.bind(this));
routerWillLeave(nextLocation){
let { dirty, dispatch, resetForm } = this.props;
if (dirty) {
let dialog = {
id: Date.now(),
showTitle: true,
titleContent: 'Unsaved Changes',
titleIcon: 'fa fa-warning',
content: <span>You have <strong>unsaved</strong> changes! <strong>Discard</strong> them?</span>,
type: 'confirm',
handleCloseClick: (e) => {
e.preventDefault();
dispatch(closeDialog());
},
acceptBtn: {
title: 'Okay',
handler: (e) => {
e.preventDefault();
resetForm();
// Wait for call stack to unwind and then execute
// the following which will now have the updated values.
setTimeout(() => {
dispatch(push(nextLocation));
dispatch(closeDialog());
}, 0);
}
},
denyBtn: {
title: 'Deny',
handler: (e) => {
e.preventDefault();
dispatch(closeDialog());
}
}
}
dispatch(addDialogWindow(dialog));
dispatch(openDialog(false, (e) => dispatch(closeDialog()), false));
return false;
}
return true;
}

Resources