This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 1 year ago.
I made a games page in react. When you win one, it displays a form dialog (with material-UI: https://material-ui.com/components/dialogs/#form-dialogs). Component's visibility depends on the open attribute, which is changed with "handleClickOpen" when you push the button. I wanted to reuse code so I made a component that contains the dialog. Here is my code so far (child class):
class Pop_dialog extends Component {
constructor(props) {
super(props)
this.state = {
open: false
}
}
handleOpen() {
console.log('A')
this.setState({ open: true })
}
handleClose() {
console.log('B')
this.setState({ open: false })
}
render() {
return (
<Dialog open={this.state.open} onClose={this.handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
<DialogContent>
<DialogContentText>
To subscribe to this website, please enter your email address here. We will send updates
occasionally.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label="Email Address"
type="email"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary">
Cancel
</Button>
<Button onClick={this.handleClose} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
)
}
I call "handleOpen" within a function in the parent class:
triggerDialog() { this.dialogRef.current.handleOpen(); }
render ()
{
...
<Pop_dialog ref={this.dialogRef}/>
}
The triggerDialog function is called when I win/lost the game and it opens the form dialog fine. The problem comes when I try to close it (with the Cancel or Subscribe buttons). The page throws the next error:
I couldnĀ“t find why it fails here but not when it use "handleOpen". By the way, this is the 4th solution that i use. I also tried using a function component with the useContext hood (It didn't work at all), passing 'open' like a prop to the child (I also could open the dialog but not close it) and turn 'open' in a session var, defined in the parent component and called in the child (I couldn't open the dialog).
I don't know if some of this ideas would work or if I need a completely new one. I am open to any kind of idea, as long as it keeps Pop_dialog reusable.
It doesn't seem as though you've bound this to the handlers in Pop_dialog. The result is that this is undefined in the callback handlers.
Bind in the constructor:
class Pop_dialog extends Component {
constructor(props) {
super(props)
this.state = {
open: false
}
this.handleOpen = this.handleOpen.bind(this);
this.handleClose = this.handleClose.bind(this);
}
handleOpen() {
console.log('A')
this.setState({ open: true })
}
handleClose() {
console.log('B')
this.setState({ open: false })
}
...
Or convert the handlers to arrow functions so this of the class is bound automatically.
class Pop_dialog extends Component {
constructor(props) {
super(props)
this.state = {
open: false
}
}
handleOpen = () => {
console.log('A')
this.setState({ open: true })
}
handleClose = () => {
console.log('B')
this.setState({ open: false })
}
...
Related
I have a parent component in which I'm struggling to properly open/close the child component (modal). The two code boxes below are simplified examples of my components.
EDIT: Here is a code sandbox with the following code -- there isn't an actual modal, however i've logged all of the stateful values that I assume will have an effect on this problem and you can see how they change/don't change as I hope they would.
Code Sandbox
When the parent component is open, I can click the MenuItem and I can see the state change, however the modal doesn't open unless I close the parent component temporarily and reopen it (then the parent component opens with the modal open already)
When the modal is open, and I try to close by clicking the close button (which has the state changing function from parent inside of the onClick method. this.state.showModal remains true, and doesn't change to false.
If I add a closeModal stateful value to the child component and change it during the close buttons onClick, this.state.showModal still remains true.
Thanks to whoever reaches out, and if you have any clarifying questions feel free to ask!
class Parent extends Component {
constructor(props) {
super(props);
this.showModal = this.showModal.bind(this);
this.closeModal = this.closeModal.bind(this)
this.state = {
showModal: false
};
this.showModal = this.showModal.bind(this)
this.closeModal = this.closeModal.bind(this)
}
showModal() {
this.setState({ showModal: true });
}
closeModal() {
this.setState({ showModal: false });
}
render() {
return (
<MenuItem onClick={this.showModal}>
<ChildComponent
prop1={prop1}
isOpen={this.state.showModal}
closeModal={this.closeModal}
/>
</MenuItem>
)}
const ChildComponent = ({
prop1,
isOpen,
closeModal
}) => {
const [modalOpen, setModalOpen] = useState(isOpen)
useEffect(() => {
setModalOpen(isOpen)
},[isOpen])
console.log('isopen on child', isOpen)
console.log('modalOpen', modalOpen)
return (
<div>
{modalOpen && (
<button
onClick={() => {
setModalOpen(false)
closeModal()
}}
>
{'click to close modal'}
</button>
)}
</div>
)}
)}
I figured out my problem!
In my parent component the onClick handler that sets the modal open wrapped my child component. I needed to remove it and conditionally render it separately like so:
<div>
<div onClick={this.showModal}>{"Click here to open modal"}</div>
{this.state.showModal && (
<ChildComponent
prop1={prop1}
isOpen={this.state.showModal}
closeModal={this.closeModal}
/>
)}
</div>
im working with the modal from https://react-bootstrap.netlify.app/components/modal/ and basically i've managed to display a modal from a button that i click. However inside the modal there's another button that when i click should perform a task i've defined in a function already. Now when i click this button in the modal i get the error cannot read property 'confirm_booking' of undefined Here is my code.
constructor(props){
super(props)
this.state={
setModalShow_available_room: false,
modalShow_available_room: false
}
this.confirm_booking = this.confirm_booking.bind(this)
}
render (){
function Available_room_modal(props) {
return (
<Modal
{...props}
size="sm"
aria-labelledby="contained-modal-title-vcenter"
centered>
<Modal.Body>
<Button block onClick={() => { this.confirm_booking() }} >Confirm</Button>
</Modal.Body>
</Modal>
);
}
return(
<div>
<Button block onClick={() => { this.open_modal() }} >Show modal</Button>
<Available_room_modal
show={this.state.modalShow_available_room}
onHide={() => {
this.setState({ setModalShow_available_room: false })
this.setState({ modalShow_available_room: false })
}} />
</div>
)
}
/**then for my functions **/
/**this opens the modal **/
open_modal() {
this.setState({ setModalShow_available_room: true })
this.setState({ modalShow_available_room: true })
}
/**this is the function assigned to the button inside the modal which throws an error when i click it**/
confirm_booking() {
this.setState({ setModalShow_available_room: false })
this.setState({ modalShow_available_room: false })
}
you are clearly not understanding how react works. Please try to see the react documentation first.
I will try to show you some of your errors:
You can't declare a function inside render method. Render method is just to return JSX code. You could declare a function in the class, return jsx from there and call it from render, that is valid.
<Button block onClick={() => { this.confirm_booking() }} >Confirm</Button>
Here, you are calling this.confirm_booking EVERY time your component is being rendered. You should change it to this:
<Button block onClick={this.confirm_booking}> Confirm </Button>
I have a reactstrap Collapse component that I'm trying to toggle state from an external Button that sits within a loop of mapped items using custom state variables.
My question:
Why does it only open and not toggle the collapse component when I have the state on my openCollapse method to setState to !state.collapse?
My code:
// some_items.js (brief example)
// State
this.state = {
toggleCollapse: false
}
// my custom state variable that I want to have toggle
openCollapse(itemId) {
this.setState(state => ({
[`toggleCollapse-${itemId}`]: !state.collapse
}));
}
// mapped item with button trigger for toggling the collapse
<div key={item.id>
<Button
onClick={() => {
this.openCollapse(item.id);
}}
>
View Listed Item Info
</Button>
//
// Some extra content that belongs here in between..
//
<ItemInfoCollapse
show={this.state[`toggleCollapse-${item.id}`]}
item={item}
/>
</div>
// My item_collapse.js
class ItemInfoCollapse extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
collapse: false,
isOpen: this.props.show
};
}
componentWillReceiveProps(nextProps) {
this.setState({ isOpen: nextProps.show });
}
toggle = () => {
this.setState({ collapse: !this.state.collapse });
};
render() {
return (
<Collapse isOpen={this.state.isOpen} className={this.props.className}>
// Some JSX markup
</Collapse>
)
}
What dictates whether your Collapse component gets open or closed is based on the show prop that you are passing into it from your parent component.
It appears you have everything set up correctly, with the exception of your state variable that you're using in your openToggle function - !state.collapse. I don't see the collapse variable anywhere which means it's undefined so it's actually running !undefined which always evaluates to true (you can test this in a browser console).
I think what you mean is !state[toggleCollapse-${itemId}] instead of !state.collapse
I am trying to make a text box auto focus.
However, I the setState is being called too late it seems.
It is being called within Popup.show. I created a button to console.log the state, and it does seem to be set to true but it must happen too late.
How can I get setState to be called as the Popup.show happens?
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
focused: false,
};
}
onClick = (event) => {
console.log('Says focussed FALSE', this.state.focused)
this.setState({ focused:true });
Popup.show(<div>
<SearchBar
autoFocus
focused={this.state.focused}
/>
<button onClick={this.checkState}>It says TRUE here</button>
</div>,
console.log('Says focussed FALSE still', this.state.focused),
{ animationType: 'slide-up'});
};
checkState = (e) =>{
console.log(this.state)
}
render() {
return (
<div style={{ padding: '0.15rem' }}>
<Button onClick={this.onClick.bind(this)}>Open & Focus</Button>
</div>);
}
}
Always remember that setState won't execute immediately. If you want Popup.show() after setState, you can use a callback:
this.setState({ focused: true }, () => {
Popup.show(...)
})
And you are already using arrow functions, you don't need the .bind(this) in your render function.
setState doesn't immediate set the state
From: https://facebook.github.io/react/docs/react-component.html#setstate
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
Changing your setState to something like
this.setState({ focused: true }, () => {
Popup.show(<div>
<SearchBar
autoFocus
focused={this.state.focused}
/>
<button onClick={this.checkState}>It says TRUE here</button>
</div>)
});
I have a react modal that renders sign-in and sign-up form.
And on the form, there is a button by id of modal-sign-in-submit-button.
When I do
document.getElementById('modal-sign-in-submit-button');
It returns a type error, saying that such element does not exist...
TypeError: document.getElementById(...) is null
Is there a package or a workaround in making it to recognize the button being in the html?
This works fine on console, but when I try to access an element rendered by React in a different script, it does not find such element. (Probably due to it not being generated yet).
Below is the source for the part,
import React, { Component } from 'react';
import Modal from 'react-modal';
class SignInModalTrigger extends Component {
constructor() {
super();
this.state = {
open: false
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({open: true});
}
closeModal() {
this.setState({open: false});
}
render () {
return (
<div>
<a className="navbar-items" onClick={this.openModal} href="#">Sign in</a>
<Modal className="sign-in-modal" isOpen={this.state.open}>
<h1 className="modal-title">Sign in</h1>
<div className="modal-inner-form">
<input className="modal-input" id="modal-sign-in-mail-input" type="email" placeholder="Enter your email here"/>
<br />
<br />
<input className="modal-input" id="modal-sign-in-password-input" type="password" placeholder="Enter your password here" pattern="(?=.*[A-Z]).{6,}"/>
<br />
<br />
<button className="modal-button" id="modal-sign-in-submit-button">Submit</button>
<br />
<br />
<button className="modal-button" onClick={this.closeModal}>Close</button>
</div>
</Modal>
</div>
);
}
}
export default SignInModalTrigger;
It doesn't look like you're creating a button with an id of 'submit-button'. You're searching for a button that doesn't exist. Try to enter the following:
document.getElementById('modal-sign-in-submit-button');
If you're looking for a framework that will allow you to identify elements on the DOM, I would recommend jQuery for dev purposes. Please note that you should try to avoid using jQuery as it is heavy and rather pointless.
I would also like to recommend that you try using lambda expressions or fat arrow functions for this.openModal & this.closeModal. This will allow you to keep the context of 'this' without the usage of .bind(). The obvious benefits are: improved code readability and the explicit binding of 'this'.
Here is an example:
openModal() => { this.setState({ open: true });
closeModal() => { this.setState({ open: false });
I hope that helps!
A noob question it was, but I found a solution in achieving what I wanted.
It's to create functions within the class itself, and then bind it to the constructor.
So something like,
constructor() {
super();
this.state = {
open: false
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
this.submit = this.submit.bind(this);
}
submit() {
const submit = document.getElementById('modal-sign-up-submit-button');
const email = document.getElementById('modal-sign-up-mail-input').value;
const pass = document.getElementById('modal-sign-up-password-input').value;
const promise = firebase.auth().createUserWithEmailAndPassword(email, pass);
promise.then(e => window.location.href="index.html");
promise.catch(e => console.log(e.message));
firebase.auth().onAuthStateChanged(firebaseUser => {
if(firebaseUser) {
this.setState({open: false});
console.log("hi");
}
});
}
render() {
return(
<button className="modal-button" id="modal-sign-up-submit-button" onClick={this.submit}>Submit</button>
)
}