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
Related
I'm using MUI Drawer in my project and i'd like to close the drawer when a user initiated event completed, my problem is the component at which the event was initiated and the compenent that render the Drawer are not the same.
Below is my ButtomNavigation.js that rendered the drawer
const ButtomNavigation = (props) => {
const [state, setState] = React.useState({
top: false,
left: false,
bottom: false,
right: false,
});
const toggleDrawer = (anchor, open) => (event) => {
if (
event &&
event.type === 'keydown' &&
(event.key === 'Tab' || event.key === 'Shift')
) {
return;
}
setState({ ...state, [anchor]: open });
};
return (
<>
<div className='d-flex justify-content-between item-bottom-nav'>
<div className='d-flex justify-content-end cart-and-buy-now'>
<button id="addCart" onClick={toggleDrawer('bottom', true)}>Add to cart</button>
<button id="buyNow" onClick={toggleDrawer('bottom', true)}>Buy now</button>
</div>
</div>
<div >
{['bottom'].map((anchor) => (
<React.Fragment key={anchor}>
<SwipeableDrawer
anchor={anchor}
open={state[anchor]}
onClose={toggleDrawer(anchor, false)}
onOpen={toggleDrawer(anchor, true)}
PaperProps={{
sx:{height: 'calc(100% - 60px)', top: 60,borderTopLeftRadius:'10px',borderTopRightRadius:'10px'}
}}
>
{props.Data !=null &&
<>
<ItemModule Data={props.Data}/>
</>
}
</SwipeableDrawer>
</React.Fragment>
))}
</div>
</>
);
};
export default ButtomNavigation;
now in my ItemModule.js component ii have a function that make a remote call to add item to cart and on success i want to close the drawer
const addToCart = async (cartItem) => {
try {
const response = await axiosPrivate.post("/cart",
JSON.stringify({ cartItem }), {
signal: controller.signal
});
console.log(response?.data);
//Here i want to close the Drawer after the response has being received
}catch(err){
handle error
}
The Drawer has an "open" property, that you're using. Move this property in a provider (in a state variable). Wrap both components in this provider, and give them access to the "open" variable, and the setOpen to the one that need it, in your case the component having "addToCart". Call setOpen when the event occured.
I want to click X Button in Card extra to visible "Confirm Remove Todo modal".
UI:
But...
the reality when I click X Button then it visible "Edit Todo modal" from Card event instead.
how can I fix it?
Code:
{todos.map(todo => (
<Card
className={styles.CardTodo}
headStyle={{ textAlign: 'left' }}
bodyStyle={{ textAlign: 'left' }}
key={todo._id}
title={todo.title}
onClick={() => handleSelectTodo(todo._id)}
extra={
<Button
type="danger"
shape="circle"
style={{ color: 'white', zIndex: 10 }}
onClick={() => handleRemoveTodo(todo._id)}
>
X
</Button>
}
>
{todo.description}
</Card>
))}
.
.
Thanks very much, guys
e.stopPropagation() is useful for me.
And then I found another problem.
It is handleRemoveTodo() is the function that opens another modal.
But that modal didn't get "Todo object"
when I remove e.stopPropagation(), the modal will get Todo Object again
Code:
Todo component
const handleRemoveTodo = () => {
setModalConfirmRemoveVisible(true)
}
const handleConfirmRemove = async todoId => {
console.log('Hello', todoId)
setIsRemoveLoading(true)
try {
await axios.delete(`/todos/${todoId}`, apiConfig)
} catch (err) {
console.error(err)
console.error(err.response.data)
}
await fetchTodos()
setModalConfirmRemoveVisible(false)
setIsRemoveLoading(false)
}
return (
{modalConfirmRemoveVisible && (
<ModalConfirmRemoveTodo
visible={modalConfirmRemoveVisible}
todo={todo}
isRemoveLoading={isRemoveLoading}
onConfirmRemoveTodo={handleConfirmRemove}
onCancel={() => setModalConfirmRemoveVisible(false)}
onConfirmRemove={handleConfirmRemove}
/>
)}
)
Modal component
const ModalConfirmRemoveTodo = props => {
const { visible, isRemoveLoading, onCancel, todo, onConfirmRemove } = props
console.log('ModalConfirmRemoveTodo', todo)
return (
<>
<Modal
visible={visible}
title={<Title level={3}>Remove Todo</Title>}
okButtonProps={{ loading: isRemoveLoading, disabled: isRemoveLoading }}
okText="Remove"
okType="danger"
onOk={() => onConfirmRemove(todo._id)}
onCancel={onCancel}
>
Want delete {todo.title} ?
</Modal>
</>
)
}
This is called Event Bubbling. When an event happens on an element, it first runs the handlers on it, then on its parent, then all the way up on other ancestors.
Please refer to this article for details: https://javascript.info/bubbling-and-capturing#bubbling
Below is my solution to your problem. Instead of opening a modal, I just use a simple alert to simulate it.
Your current problem: https://codesandbox.io/s/event-bubbling-bojvq
You will see that the Chrome alert will pop up twice. The former is from the onClick of extra, the latter is from onClick of Card.
Solution: https://codesandbox.io/s/prevent-bubbling-zkxk6
Just add a simple e.stopPropagation() to prevent the bubbling inside extra Button onClick. Please refer to this: https://javascript.info/bubbling-and-capturing#stopping-bubbling for more information.
Back to your code, just simply update your Button's onClick like this:
onClick={e => { e.stopPropagation(); handleRemoveTodo(todo._id)}}
Use stopPropagation() method on your event:
<Button
type="danger"
shape="circle"
style={{ color: 'white', zIndex: 10 }}
onClick={e => { e.stopPropagation(); handleRemoveTodo(todo._id)}}
>
X
</Button>
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.
I am using react material and the popper component in my project. I am trying to test component that uses popper with unit test.
This are the methods in the component:
constructor(props) {
super(props);
this.state = {
openMenu: false,
};
}
onToggleMenu() {
this.setState(prevState => ({openMenu: !prevState.openMenu}))
}
onCloseMenu() {
this.setState({openMenu: false})
}
And this is the part of the render where I use popper:
<IconButton
classes={{root: classes.expandButton}}
aria-label="menu"
buttonRef={node => this.anchorEl = node}
aria-owns={openMenu ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={() => this.onToggleMenu()}
>
<ArrowDropDownIcon fontSize="large"/>
</IconButton>
<Popper open={openMenu} anchorEl={this.anchorEl} placement="bottom-end" transition disablePortal>
{({TransitionProps, placement}) => (
<Grow
{...TransitionProps}
id="menu-list-grow"
style={{transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom'}}
>
<Paper>
<ClickAwayListener onClickAway={() => this.onCloseMenu()}>
<MenuList disablePadding>
<MenuItem onClick={onDeaktiver}>Deaktiver</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
I have tried to test this like this:
it('opens the menu', async () => {
const header = editor.findByType(EditorHeader);
expect(header.instance.state.openMenu).toBeFalsy();
const menuButton = header.findByProps({'aria-label': 'menu'});
menuButton.props.onClick();
expect(header.instance.state.openMenu).toBeTruthy();
})
But, then I get the error:
Error: Failed: "Error: Uncaught [TypeError: Cannot set property
'transition' of undefined]
How can I mock popper or fix this so that I have this test work?
Had the same problem with MUI Menu component.
It seems there is a known issue with react-transition-group. There are few workarounds posted in the ticket, but most of them didn't work for me, so I've ended up mocking the MUI component.
jest.mock('#material-ui/core', () => {
const materialUI = jest.requireActual('#material-ui/core');
return {
...materialUI,
Menu: jest.fn(({ children, open }) => (open ? children : null)),
};
});
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>
);