react-bootstrap-sweetalert how to disable submit button after first click? - reactjs

I have a problem with react-bootstrap-sweetalert library in react. Actually it works fine, untill slow internet connection. When someone tries to click submit button, because of the slow internet (I'm simulating it through "Network section [Slow 3G]") alert is not closing exactly at time after clicking a button, but after several seconds. So, there is probability that someone can click several times submit button. It is a problem, because several same requests can flow to backend and database. In other sections without using a library I can just "disable" react states after handling onClick.
So question is - to disable button in react-bootstrap-sweetalert library after handling onConfirm function.
Code:
handleSubmitInvoice = () => {
this.setState({
sweetalert: (
<SweetAlert
warning
showCancel
confirmBtnText={this.state.alert.label.sure}
cancelBtnText={this.state.alert.label.cancel}
confirmBtnBsStyle="success"
cancelBtnBsStyle="default"
disabled={disableButton}
title={this.state.alert.label.areyousure}
onConfirm={() => this.submit()}
onCancel={() => this.hideAlert()}
>
{this.state.alert.confirmSubmit}
</SweetAlert>
)
});
};
in render():
<button
className="btn btn-success btn-sm"
onClick={this.handleSubmitInvoice}
>
submit
</button>
submit function:
submit = () => {
const req = { invoice: this.state.invoiceNumber };
Axios.post("/api", req)
.then(() => {
this.props.history.push({
pathname: "/mypathname",
state: {
fromSubmitInvoice: true
}
});
})
.catch(err => {
Alert.error(
err.response.data.code === "internal_error"
? this.state.alert.raiseError
: err.response.data.text,
{
position: "top-right",
effect: "bouncyflip",
timeout: 2000
}
);
this.hideAlert();
});
};
Codesandbox: https://codesandbox.io/s/sweet-alert-problem-ktzcb
Thanks in advance.

Problem solved try this out
import React, { Component } from "react";
import SweetAlert from "react-bootstrap-sweetalert";
import ReactDOM from "react-dom";
const SweetAlertFunction = ({ show, disableButton, submit, hideAlert }) => {
return (
<SweetAlert
warning
show={show}
showCancel
confirmBtnText="confirmBtnText"
cancelBtnText="cancelBtnText"
confirmBtnBsStyle="success"
cancelBtnBsStyle="default"
disabled={disableButton}
title="title"
onConfirm={submit}
onCancel={hideAlert}
>
submit
</SweetAlert>
);
};
export default class HelloWorld extends Component {
constructor(props) {
super(props);
this.state = {
disableButton: false,
show: false
};
}
hideAlert() {
this.setState({
show: false
});
}
submit() {
this.setState({ disableButton: true });
console.log("submit");
setTimeout(() => {
this.setState({ disableButton: false });
}, 3000);
}
render() {
const { show, disableButton } = this.state;
console.log("disableButton", disableButton);
return (
<div style={{ padding: "20px" }}>
<SweetAlertFunction
show={show}
disableButton={disableButton}
submit={() => this.submit()}
hideAlert={() => this.hideAlert()}
/>
<button
className="btn btn-success btn-sm"
onClick={() => this.setState({ show: true })}
>
Click
</button>
</div>
);
}
}
ReactDOM.render(<HelloWorld />, document.getElementById("app"));

In your case, since you are assigning the Sweetalert component to the sweetalert state, you need to have a local state that controls the disabled state, but to make it simple, you can make sweetalert state control the visibility/presence of the Sweetalert component, like below:
handleSubmitInvoice() {
// just set sweetalert to true to show the Sweetalert component
this.setState({ sweetalert: true });
}
render() {
const { sweetalert, disableButton } = this.state;
return (
<div style={{ padding: "20px" }}>
// this makes disableButton reactive and pass it automatically to Sweetalert component
{sweetalert && (
<SweetAlert
warning
showCancel
confirmBtnText="confirmBtnText"
cancelBtnText="cancelBtnText"
confirmBtnBsStyle="success"
cancelBtnBsStyle="default"
disabled={disableButton}
title="title"
onConfirm={() => this.submit()}
onCancel={() => this.hideAlert()}
>
submit
</SweetAlert>
)}
<button
className="btn btn-success btn-sm"
onClick={this.handleSubmitInvoice}
>
Click
</button>
</div>
);
}
You can see it in this sandbox https://codesandbox.io/s/sweet-alert-problem-lv0l5
P.S. I added setTimeout in submit to make disabling of button noticeable.

Related

Toggle in react for multiple buttons

Onclick function execute the myChangeHandler, which changes the state to opposite on every click. This will toggle the content inside h1 element. Here the function execute the change for both button. Any possibility to change that behaviour for individual button?
class File extends React.Component {
constructor(props) {
super(props);
this.state = {
user: false,
admin:false
};
this.myChangeHandler = this.myChangeHandler.bind(this);
}
myChangeHandler() {
this.setState(state => ({
user:!state.user
admin:!state.admin
}));
}
render() {
return(
<div> <button onClick={this.myChangeHandler}>Toggle admin </button>
{this.state.display && <h1>admin online!</h1>} </div>
<div> <button onClick={this.myChangeHandler}>Toggle user </button>
{this.state.display && <h1>user online!</h1>} </div>
)
}
}
You can give the buttons a name and access those in the handler:
<button name='admin' onClick={this.myChangeHandler}>Toggle admin </button>
myChangeHandler(e) {
const id = e.target.name
this.setState((state) => ({
[id]: !state[id]
}));
}
Note that you have to save the id before the setState, because setState is async and the event will be removed after the function. So if you try to access the event during the delayed setState, the name would be null.
Sandbox
You can pass a reference to the function to tell which button you clicked on:
myChangeHandler(name) {
this.setState((prev) => ({ [name]: !prev[name] }));
}
render() {
return(
<div>
<button onClick={() => this.myChangeHandler('admin')}>Toggle admin </button>
{this.state.display && <h1>admin online!</h1>}
</div>
<div>
<button onClick={() => this.myChangeHandler('user')}>Toggle user</button>
{this.state.display && <h1>user online!</h1>}
</div>
)
}

hide and show external components in react

I am newbie in react and I have some trouble that I'd like to solve.
I would like to know how can I show and hide react components before and after to do a rest call.
I have the follow component:
class Loading {
render(){
return (
<div >
<Modal isOpen={true} centered >
<ModalHeader>Loading...</ModalHeader>
<ModalBody >
<div align='center' className="mt-2 mb-2">
<Spinner style={{ width: '4rem', height: '4rem' }} color="primary" />
</div>
</ModalBody>
</Modal>
</div>
)
}
}export default Loading;
And I would like to show this component in other module before to call a rest api and hide this component after the data come. The ideia is some like this:
class List extends Component {
constructor(props) {
super(props)
this.state = {
show : false
}
}
// HERE I WOULD LIKE TO SHOW THE LOADING COMPONENT
callRestApi = () => {
axiosAuth.get(url, getConfig())
.then(response => {
console.log(response.data)
this.setState({
eventos: response.data
})
}).catch(error => {
console.log(error);
return null
});
// HERE I WOULD LIKE TO HIDE THE LOADING COMPONENT
}
render() {
return(
<div>
<Button className="mr-2" color="primary" size="sm" onClick={this.callRestApi}>List All</Button>
</div>
How can I do it?
You can create state that dictates whether the loading spinner is visible or not. And append one last .then in the promise chain to modify it.
class List extends Component {
constructor(props) {
super(props)
this.state = {
show : false,
loaderVisible: true
}
}
// HERE I WOULD LIKE TO SHOW THE LOADING COMPONENT
callRestApi = () => {
axiosAuth.get(url, getConfig())
.then(response => {
console.log(response.data)
this.setState({
eventos: response.data
})
}).catch(error => {
console.log(error);
return null
}).then(() => {
this.setState({loaderVisible: false });
});
}
render() {
return(
<div>
{
this.state.loaderVisible? <Loading /> : ''
}
<Button className="mr-2" color="primary" size="sm" onClick={this.callRestApi}>List All</Button>
</div>
Then utilize ternary syntax on the spinner to determine visibility.
We use state to implement this. Here is the pseudo code.
class List extends Component {
state = { loading: false }
callRestApi = async () => {
this.setState({ loading: true });
await fetch(...);
this.setState({ loading: false });
}
render() {
<div>
{this.state.loading && <Loading />}
<button onClick={this.callRestApi}>List All</button>
</div>
}
}

How do I get my dropdown in react to update without having to refresh?

I have a dropdown in react that I am able to add and delete options to perfectly fine, however, my problem is that the dropdown only updates after I refresh the page. I want it to reflect the new addition or deletion automatically after pressing either the add or delete buttons. I believe the answer has to do with updating the state after the onSubmit and deleteRole methods have been called. I am fairly new to react and would appreciate any guidance. Thanks in advance. Also here is my code and here is a link to what the dropdown looks like: http://a85febb5.ngrok.io/roles
import React, { Component } from 'react';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
import InputGroup from 'react-bootstrap/InputGroup';
import FormControl from 'react-bootstrap/FormControl'
import axios from 'axios';
import Select from 'react-select';
// modal docs: https://react-bootstrap.github.io/components/modal/
class Rolesmodal extends Component {
constructor(props) {
super(props);
this.updateInput = this.updateInput.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.deleteRole = this.deleteRole.bind(this)
this.state = {
show: false,
role: '',
roles: [],
selectedOption: null,
id: null,
}
}
// opens and closes the modal on click of Edit Roles Button
handleClose = () => {
this.setState({ show: false})
}
handleShow = () => {
this.setState({ show: true})
}
// gets roles data from database
componentDidMount() {
axios.get('http://localhost:4000/roles')
.then(res => {
this.setState({ roles: res.data });
})
.catch(function (error) {
console.log(error);
})
}
// When a role is selected in the dropdown the id of that role is set in the state
handleChage = (selectedOption) => {
this.setState({ selectedOption });
this.setState({ id: selectedOption.id})
}
// creates an array of roles
rolesList() {
return this.state.roles.map(currentrole => ({
label: currentrole.role_title, value: currentrole.role_title, id: currentrole._id
}))
}
updateInput = (e) => {
this.setState({
role: e.target.value
})
}
Capitalize(str){
return str.charAt(0).toUpperCase() + str.slice(1);
}
onSubmit() {
const roleadd = {
role_title: this.Capitalize(this.state.role)
}
console.log(roleadd);
axios.post('http://localhost:4000/roles/add', roleadd)
.then(res => console.log(res.data));
this.setState({
role: '',
})
}
deleteRole(id) {
// remove deleted item from state
axios.delete('http://localhost:4000/roles/'+this.state.id)
.then(response => { console.log(response.data)})
this.setState({
roles: this.state.roles.filter(el => el.id !== id)
})
}
render() {
console.log(this.state.id)
const roles = this.rolesList()
console.log(roles)
console.log(this.state.roles)
return (
<>
<Button style={{marginLeft: '20px'}} variant="outline-success" onClick={this.handleShow}>
Edit Roles
</Button>
<Modal show={this.state.show} onHide={this.handleClose} animation={true}>
<Modal.Header closeButton>
<Modal.Title>Edit Roles</Modal.Title>
</Modal.Header>
<Modal.Body style={{height: '300px'}}>
<div style={{display: 'flex',justifyContent: 'center', marginBottom: '20px' }}>
<InputGroup className="mb-3">
<FormControl
role={this.state.role}
value={this.state.role}
onChange={this.updateInput}
style={{width: '400px'}}
placeholder="Add new role"
aria-label="Add new role"
aria-describedby="basic-addon2"
/>
<Button onClick={this.onSubmit} style={{marginLeft: '10px', width: '75px'}} variant="success">Add</Button>
</InputGroup>
</div>
<div style={{display: 'flex',justifyContent: 'center'}}>
<div style={{width: '400px'}}>
<Select onChange={this.handleChage} options={ roles } />
</div>
<Button onClick={this.deleteRole} style={{marginLeft: '10px'}} variant="danger">Delete</Button>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="outline-secondary" onClick={this.handleClose}>
Close
</Button>
<Button variant="outline-primary" onClick={this.handleClose}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
</>
);
}
}
export default Rolesmodal;
onSubmit() {
const roleadd = {
role_title: this.Capitalize(this.state.role)
}
console.log(roleadd);
axios.post('http://localhost:4000/roles/add', roleadd)
.then(res => console.log(res.data));
this.setState({
role: '',
roles: [...roles, roleadd]
})
Also update the roles array to reflect the added role
The dropdown component get the value, but not rendering it. Since it will shallow comparing the old data prop with new one which is object reference thus it will think that nothing has changed and won't re-render the child component; in order to solve that you need to force react to re-render your component on every parent component render; React will remount the component if its key change;
a simple solution is to add key to your select component (Ideally based on roles therfore only changes when a role is changed) but if you can't figure out a perfect key value, you can use a new key on every render based on a random value, like:
<Select key={Math.floor(Math.random() * 99999) onChange={this.handleChage} options={ roles } />
this key will definitely change on every render of parent but my not be most efficient way; however for simple use-case like that is fine;

How to handle dialog state outside of dialog component?

I have the following dialog component:
class LoginDialog extends React.Component {
state = {
open: false,
};
openDialog = () => {
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
render() {
return (
<div>
<Dialog
open={this.state.open}
onClose={this.handleClose}
>
<DialogActions>
<Button onClick={this.handleClose} color="primary">
Cancel
</Button>
<Button onClick={this.handleClose} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
How can I open that dialog from parent component AND ensure the close dialog also works? This is my attempt
class MainAppBar extends React.Component {
state = {
openLoginDialog: false,
openRegisterDialog: false
};
render() {
return (
<div>
<Button color="inherit" onClick={this.state.openLoginDialog}>Login</Button>
)}
<LoginDialog /*not sure how to pass here openLoginDialog*//>
</div>
);
}
}
So I am not sure whether I really have to keep dialog states in both child/parent and how to properly open it from parent.
You have to maintain the state whether the login dialog is open or not in the parent. Pass the open/close status to the child, and the callback to close the dialog to the child via props.
class MainAppBar extends React.Component {
state = {
openLoginDialog: false,
openRegisterDialog: false
};
openLoginDialog = () => {
this.setState({
openLoginDialog: true
});
};
closeLoginDialog = () => {
this.setState({
openLoginDialog: false
});
};
render() {
return (
<div>
<Button color="inherit" onClick={() => this.openLoginDialog()}>
Login
</Button>
)}
<LoginDialog
closeLoginDialog={this.closeLoginDialog}
isLoginDialogOpen={this.state.openLoginDialog}
/>
</div>
);
}
}
This component doesn't need any state management since we're managing it in the parent. We can make is pure this way:
const LoginDialog = props => (
<div>
<Dialog open={props.isLoginDialogOpen} onClose={props.closeLoginDialog}>
<DialogActions>
<Button onClick={props.closeLoginDialog} color="primary">
Cancel
</Button>
<Button onClick={props.closeLoginDialog} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
Hope this is helpful!
If you let the parent component manage the dialog's status, you can allow it full control over it, while passing the control function to the dialog element itself:
class MainAppBar extends React.Component {
constructor(props) {
this.state = {
openLoginDialog: false,
openRegisterDialog: false
};
}
closeDialog() { // This method will be passed to the dialog component
this.setState({
openLoginDialog: false
});
}
render() {
return (
<div>
<Button color="inherit" onClick={this.state.openLoginDialog}>Login</Button>
)}
<LoginDialog isOpen={this.state.openLoginDialog} closeDialog={this.closeDialog}>
</div>
);
}
}
class LoginDialog extends React.Component {
render() {
return (
<div>
<Dialog
open={this.props.isOpen}
onClose={this.props.closeDialog}
>
<DialogActions>
<Button onClick={this.props.closeDialog} color="primary">
Cancel
</Button>
<Button onClick={this.props.closeDialog} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
You could define handleClose() or an equivalent an event-handler inside MainAppBar component and pass that down to the child. It can manage the state-variables (true/false) on the Parent and pass that boolean value into LoginDialog bar to determine if they should be open. That way the state of the child will be managed by the parent.
class MainAppBar extends React.Component {
state = {
openLoginDialog: false,
openRegisterDialog: false
};
toggleDialog = () => {
this.setState((prevState) => {
return{
openLoginDialog: !prevState.openLoginDialog
}
})
}
render() {
return (
<div>
<Button color="inherit" onClick={this.state.openLoginDialog}>Login</Button>
)}
<LoginDialog open={this.state.openLoginDialog} toggle={this.toggleDialog}/>
</div>
);
}
}
Then:
class LoginDialog extends React.Component {
render() {
return (
<div>
<Dialog
open={this.props.open}
onClose={() => this.props.toggle} //not sure what this listener does, but im assuming you want to close it
>
<DialogActions>
<Button onClick={() => this.props.toggle} color="primary">
Cancel
</Button>
<Button onClick={() => this.props.toggle} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
I will take a different approach than the other answers and only include LoginDialog when it's needed.
We can now make LoginDialog a functional component and lift the state up to the Parent component. now our LoginDialog is much simpler and easier to test and doesn't depend on anything
class Parent extends React.Component {
state = {
isOpen: false,
};
// No need to use open and close handler because if the modal
// is open another execute of the function will close it
// this way we can still toggle it from the button that's opening the Dialog
toggleDialog = () => {
this.setState(prevState => ({
open: !prevState.open,
}));
};
// if you want make the handler more flexible you can write it like this
// make it a toggle by default with an optional nextState to
// make it more flexible
dialogStateHandler = (nextState) => () => {
this.setState(prevState => ({
open: nextState || !prevState.open,
}));
};
// to use this handler you will need to invoke it and passing
// in the nextState or without to make it toggle
// onClick={this.dialogStateHandler(true / false || without args to toggle)}
render() {
const { isOpen } = this.state;
return (
<div>
<button onClick={this.toggleDialog}>Toggle</button>
{/* include the Dialog component only when its open */}
{isOpen && <LoginDialog closeDialog={this.toggleDialog} />}
</div>
);
}
}
Receive closeDialog as props from Parent and pass it down to Child components
const LoginDialog = ({ closeDialog }) => (
<div>
<Dialog
closeDialog={closeDialog}
>
<DialogActions>
<Button onClick={closeDialog} color="primary">
Cancel
</Button>
<Button onClick={closeDialog} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
)}
</div>
);

React unmount a Paypal button

I have a Paypal button rendered with a Modal component. What is the proper way to unmount the Paypal Button without raising a clean up error?
Here is the implementation for the Dialog
<Drawer anchor="bottom" open={open} onClose={() => setStatus(false)}>
<section className={classes.innerDrawer}>
<h2 className={classes.innerDrawerTitle}>
{loading ? '' : 'Checkout'}
</h2>
<PaypalButton
...props
/>
</section>
</Drawer>
And the button
const Button = paypal.Button.driver('react', { React, ReactDOM });
return (
<Button
env={PAYPAL_ENV}
client={client}
payment={(data, actions) => payment(data, actions)}
onAuthorize={data => execute(data.payerID, data.paymentID)}
style={{
size: 'medium', // tiny, small, medium
color: 'blue', // orange, blue, silver
shape: 'rect', // pill, rect
}}
/>
);
The error message I get:
Uncaught Error: No response from window - cleaned up
I do not get this error message when the un-mount is successful, which happens when I processed with a payment.
link:
https://codesandbox.io/s/r4zvkjm2kq
I couldn't reproduce your issue, but I've tried to do the same code you're doing.
In this example, the PayPal button is mounted in the Drawer element, which is mounted after a button click. The Drawer is unmounted when you click in any place outside the drawer.
class Modal extends React.Component {
constructor() {
super()
this.state = {
open: false
}
}
render () {
return (
<div>
<button onClick={() => this.setState({ open: true })}>Open Drawer</button>
{
this.state.open &&
<Drawer anchor="left" open={true} onClose={() => this.setState({ open: false })}>
<PayPalButton
commit={this.state.commit}
env={this.state.env}
client={this.state.client}
payment={(data, actions) => this.payment(data, actions) }
onAuthorize={(data, actions) => this.onAuthorize(data, actions)}
/>
</Drawer>
}
</div>
)
}
}
Working demo: https://codepen.io/herodrigues/pen/gqQEgr

Resources