ReactJS Show confirmation before submitting a form - javascript

So I am trying to show a custom confirmation modal to ask if the user really wanted to submit the form.
I am using the onBefore callback to show the confirmation but cannot figure out how can I show a custom modal component instead of the standard window.confirm dialog, is it event possible to do such thing?
post('/submit', {
data: data,
// Confirm message before actually submitting form
onBefore: window.confirm('Submit?'), // show modal here instead of window.confirm
// Clear inputs on successful submits
onSuccess: e.target.reset()
})

Yes, you can create custom html with action buttons (cancel & confirm) base on that button return the boolean value and attach it to the dom using the async/await or Promise functions and call in onBefore event.
Alternatively, upon form submission, open the confirmation modal component via state change; following user confirmation, access the API.
Alternately, you might want to look into SweetAlert as a way to save some time. It is quite configurable and beautiful in its default form.
Confirm Example
sweetAlert(
{
title: "Are you sure?",
text: "You will not be able to recover this imaginary file!",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "Yes, delete it!"
},
apiCall()
);

I am extending Naftalib's answer with actual code.
import React, { useState, useRef } from "react";
import Button from "react-bootstrap/Button";
import Modal from "react-bootstrap/Modal";
function Page() {
const [show, setShow] = useState(false);
const form = useRef(null);
const handleClose = () => setShow(false);
const handleFormSubmit = (e) => {
e.preventDefault();
setShow(true);
};
const submitForm = () => {
form.current.submit();
};
return (
<>
<form ref={form} onSubmit={handleFormSubmit}>
...
</form>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>Do you really want to submit?</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" onClick={submitForm}>
Confirm
</Button>
</Modal.Footer>
</Modal>
</>
);
}

I recently did this with a simple react-bootstrap modal
https://react-bootstrap.netlify.app/components/modal/
set the visibility to some state variable boolean (show={stateVariableX})
and the confirm button can trigger the submit form function as well as set modal visibility to false

If understanding correctly then it's question just a piece of cake. you could call it inside the onBefore callback, and pass a callback function to the modal component to handle the result of the confirmation like this.
import React, { useState } from 'react';
function ConfirmModal({ onConfirm }) {
const [visible, setVisible] = useState(false);
const handleConfirm = () => {
onConfirm(true);
setVisible(false);
};
const handleCancel = () => {
onConfirm(false);
setVisible(false);
};
return visible ? (
<div>
<p>Submit?</p>
<button onClick={handleConfirm}>Confirm</button>
<button onClick={handleCancel}>Cancel</button>
</div>
) : null;
}
post('/submit', {
data: data,
// Confirm message before actually submitting form
onBefore: (cb) => {
setConfirmModalVisibility(true);
setOnConfirm(cb);
},
// Clear inputs on successful submits
onSuccess: e.target.reset()
})
function MyForm() {
const [confirmModalVisibility, setConfirmModalVisibility] = useState(false);
const [onConfirm, setOnConfirm] = useState(null);
return (
<form>
{/* form inputs */}
<ConfirmModal visible={confirmModalVisibility} onConfirm={onConfirm} />
</form>
);
}

Related

Why autofocus not working on opened modal that created by flowbite modal?

I am trying to use autofocus functionality in my input area. I have a modal open when I click on a button. Inside modal component I have a input area that created using react-hook-forms for validation. I tried to use useRef to check if my modal rendered, after render completed, I am trying to use autofocus using setFocus from react-hook-forms but it is not working.
Here my some part of my code
export const FileListContextModal = <InputName extends string>({
inputProps: { defaultValue, ...inputProps },
title,
onClose,
onSubmit,
cancelLabel,
saveLabel,
...modalProps
}: FileListContextModalProps<InputName>) => {
const { handleSubmit, control, setError, setFocus } = useForm<
Record<string, string>
>({
defaultValues: { [inputProps.name]: defaultValue || '' },
});
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (inputRef.current) {
setFocus(inputProps.name);
}
}, [inputRef]);
return (
<Modal {...modalProps} onClose={onClose} onClick={preventPropagation}>
<Modal.Header>{title}</Modal.Header>
<form onSubmit={handleSubmit(onSubmitHandler)}>
<Modal.Body>
<FormTextInput control={control} {...inputProps} ref={inputRef} />
</Modal.Body>
<Modal.Footer>
<Button type="button" color="light" onClick={onClose}>
{cancelLabel}
</Button>
<Button type="submit">{saveLabel}</Button>
</Modal.Footer>
</form>
</Modal>
);
};
as you see it is not focusing in my input area
I used setTimeOut inside useEffect but I dont know how much it is right to give time and focus on the input area after a while. Here the working code snippet
useEffect(() => {
setTimeout(() => {
setFocus(inputProps.name);
}, 5);
}, [setFocus, inputProps.name]);
Is there any other solution other than using setTimeOut here ??

how to conditionally render dialog Material-ui

I am trying to show a dialog box based on the data returned from apollo hook, where I would have to check that one of the values matches an id.
When checker===true I want the dialog to open on render and when the user clicks the Close button, the dialog should close.
const DialogComponent = () => {
const {data, loading, error} = useQuery(GET_QUERY_DATA)
const [isDialogOpen, setIsDialogOpen] = useState(false);
const checker = data && data.getData.some((item_data.id === id))
const closeDialog = () => {
setIsDialogOpen(false)
}
if(checker) {
setIsDialogOpen(true)
}
return(
<Dialog
open={isDialogOpen}
close={closeDialog}>
// dialog content here
<DialogActions>
<Button onClick={closeDialog}> Close </Button>
</DialogActions>
</Dialog>
)}
The above errors with too many re-renders.
I have tried a conditional render instead however, seems that the Dialog component never opens even when checker===true (below).
const DialogComponent = () => {
const {data, loading, error} = useQuery(GET_QUERY_DATA)
const [isDialogOpen, setIsDialogOpen] = useState(false);
const checker = data && data.getData.some((item_data.id === id))
const closeDialog = () => {
setIsDialogOpen(false)
}
if(checker) {
setIsDialogOpen(true)
}
return(
{checker && <Dialog
open={isDialogOpen}
close={closeDialog}>
// dialog content here
<DialogActions>
<Button onClick={closeDialog}> Close </Button>
</DialogActions>
</Dialog>
)}}
I have also tried replacing the open prop value with checker I.e. open={checker} however, then the Dialog box never can be closed even when clicking the Close button.
Any help appreciated.
The close button does close the dialog, it is being opened again on the next render with
if(checker) {
setIsDialogOpen(true)
}
you could do:
<Dialog
open={isDialogOpen && checker}
close={closeDialog}>
<DialogActions>
<Button onClick={closeDialog}> Close </Button>
</DialogActions>
</Dialog>
One problem I see in your code is regarding this part:
if (checker) {
setIsDialogOpen(true)
}
Every time a state is updated in a component, the component funcion is called again to re-render it with the updated state. So the snippet above is executed again and if checker is true, the state is updated again, then it keeps re-redering again and again.
Try wrapping the snippet above inside a React.useEffet() like this:
React.useEffect(() => {
setIsDialogOpen(checker)
}, [checker])

history.push is not working after using history.block

I am trying to show a message when user try to leave current page, so I am using history.block like this:
import { useHistory } from "react-router-dom";
const ProfilerCreate = ({ pageType }) => {
const history = useHistory();
const [isDisabled, setIsDisabled] = useState(true);
const [openModalUnsave, setOpenModalUnsave] = useState(false);
useEffect(() => {
history.block(validateChange);
}, []
);
//Function to validate changes and open modal
function validateChange(txt) {
if (!isDisabled) {
toggleModalUnsave();
return false;
}
}
//Function to open or close modal
function toggleModalUnsave() {
setOpenModalUnsave(!openModalUnsave);
}
//Function to return landing page
function returnPage() {
history.push("/");
}
return (
...
<div style={{ display: "none" }}>
<Modal
id="myModal"
heading="You have unsaved changes"
description="Do you want to save or discard them?"
isOpen={openModalUnsave}
onRequestClose={(detail) => toggleModalUnsave()}
actionsRight={
<>
<Button display="text" onClick={() => returnPage()}>
Discard
</Button>
<Button
display="primary"
onClick={(evt) => saveAudienceData(evt)}
>
Save and exit
</Button>
</>
}
>
<p>Modal Children</p>
</Modal>
</div>
);
export default ProfilerCreate;
when it is detecting unsaved changes, it shows a modal with a warning and two buttons, one for save and the other for discard, when the user hit discard button it should return to home page, but history.push is not working.
I tried to find the solution or I don't know if I am using the history.block in a wrong way.
I hope that you can help me, thanks!
I think you are missing the unblock() method in validateChange(txt)

Set button timeout - to disable for log in

How can I set the timeout for the button to be disabled immidetaly after it was clicked to avoid people signing up twice?
Currently I have a function below:
timeout_init = () => {
setTimeout("timeout_trigger()", 2000);
};
<button
className="button__submit"
type="submit"
onClick={this.timeout_init}
>Submit
</button>
How can I add a disabling button into that?
Thanks
In React you should use states to keep track wether something should be disabled or not.
In example below I use the isDisabled state to disable or enable the button. Whenever the button is clicked setIsDisabled is set to true and therefor the button will now be disabled.
I don't know why you would need a setTimeout as you state "..to be disabled immediately after it was clicked..". This does just that.
const ExampleComponent = () => {
const [isDisabled, setIsDisabled] = useState(false);
const handleClick = () => {
setIsDisabled(true);
};
return (
<button
className="button__submit"
type="submit"
onClick={handleClick}
disabled={isDisabled}
>
Submit
</button>
)
};
The "disabled" state of the button is just that... state. You'd track it like any other state, for example:
// using hooks
const [isDisabled, setIsDisabled] = useState(false);
// OR if you're using old class-based components
this.state = { isDisabled: false };
And use it in the component:
<button
className="button__submit"
type="submit"
onClick={this.timeout_init}
disabled={isDisabled}
>
Submit
</button>
And you can update that state in your click handler:
// using hooks
timeout_init = () => {
setIsDisabled(true);
setTimeout(timeout_trigger, 2000);
};
// OR if you're using old class-based components
timeout_init = () => {
this.setState({ isDisabled: true });
setTimeout(this.timeout_trigger, 2000);
};
It's not clear what timeout_trigger is, but if that's where you'd want to re-enable the button then you'd setIsDisabled(false) in that function as well.
Instead of disable a button add a overlay on click on login button.
If the request is failed remove the overlay so that user can change username/password in the login form.
I the request is success, any how user will be redirected to new page.
use the event object to capture the button element then disable it after you call the setTimeOut(yourCb):
const Btn = (props) => {
const cb =() => {
console.log("Testing the button")
}
const timeout_init = (e) => {
setTimeout(cb, 2000);
e.target.disabled = true
}
return (
<button onClick={timeout_init}> Click to disable </button>
)
}
ReactDOM.render(<Btn/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'></div>

How to trigger API inside React Modal only when Modal is opened?

I am working on a REACT based web-app POC in my org. There is table of issues and for each of these issues, I have to provide a button in the table which when a user clicks on - it will open up a modal, fetch data for that issue via an API call and then broadcast the data in that modal.
The problem:
Let's say I have 300 issues listed in that table, hence there are 300 clickable buttons for opening modals and calling API. Now the problem is that, whenever that table loads, it calls APIs for all 300 issues at once, but I want each API to only be called when an user clicks on the respective button!
Here is the code for Modal component which I have managed so far:
import React, { FunctionComponent, useState, useEffect } from 'react'; // importing FunctionComponent
import { Modal, Button } from 'react-bootstrap';
type IssueReportProps = {
issueInfo: any
}
const IssueReport: FunctionComponent<IssueReportProps> = ({ issueInfo }) => {
const issueNumber: string = issueInfo.number;
const [show, setShow] = useState(false);
const [diagnosisInfo, setdiagnosisInfo] = useState({});
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
useEffect(() => {
async function fetchData() {
const res = await fetch("http://api-call/?issuenumber=".concat(issueNumber));
res.json().then(res => setdiagnosisInfo(res));
}
fetchData();
}, [issueNumber]);
console.log(diagnosisInfo);
return (
<>
<Button variant="outline-primary" onClick={handleShow} size="sm">
Diagnosis
</Button>
<Modal show={show} onHide={handleClose} backdrop="static" keyboard={false}>
<Modal.Body>
<p>
Issue Info: {JSON.stringify(diagnosisInfo)}
</p>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</>
);
};
export default IssueReport;
The console.log(diagnosisInfo); confirms my suspicions that once the issue is loaded, APIs for all issues are called. How to prevent this?
Please let me know if I need to provide more details.
EDIT1: Accepted Solution-
Here is the change I made to the code post #Dykotomee's solution:
// useEffect:
useEffect(() => {
async function fetchData() {
const res = await fetch("http://api-call/?issuenumber=".concat(issueNumber));
res.json().then(res => setdiagnosisInfo(res));
}
// fetchData();
if (show){
fetchData();
}
}, [issueNumber, show]);
useEffect is called every time the component renders. Therefore, when the table loads with 300 components, the API is fetched 300 times!
You only want to fetch if the modal is showing. Try wrapping your call to fetchData in a conditional:
if (show) {
fetchData();
}
It's not ideal, considering the modal will likely show prior to the fetch being completed, but you can adjust your code or add more states to compensate.

Categories

Resources