React: How to close a modal from child opened from parent component - javascript

I am opening child component modal by passing parent state as props to child. Is there any way to close the modal from child component itself without any interference from parent component.
class Parent extends Component {
constructor(props) {
super(props);
this.showModal = this.showModal.bind(this);
this.state = {
showModal: false
};
}
showModal() {
this.setState({ showModal: true });
}
renderRow() {
return (
<tr>
<td onClick={() => this.setState({ show: true })}>test</td>
<ChildModal show={this.state.showModal}/>
</tr>
);
}
}
class ChildModal extends Component {
render() {
return (
<Modal show={this.props.showModal}>
<Modal.Header closeButton>
<Modal.Title>Test</Modal.Title>
</Modal.Header>
<Modal.Body>
{/* some text */}
</Modal.Body>
</Modal>
);
}
}
I want my child modal to be self contained. Is this even possible in react.

You need to pass a callback as a props in Child component, it will update Parent Component when you click on closeButton in child.
// Parent Component
callbackModal = () => {
this.setState({ showModal: false });
}
//ChildButton
closeButtonClickHandler = () => {
this.props.callbackModal();
}

Yes, you can close it from the child component, but you'll need at least a little interference of parent component, and that is because you've defined the toggle state of this model in parent component.
simply define a method that will close the modal in parent component, pass it down to the child component as a prop, and call it there.
//in your parent component
handleModalClose = ()=>{
this.setState({showModal: false})}
now pass it down to your child component and simply call it there on an event like
this.props.handleModalClose()

A local state variable can only be controlled inside the component in which it was declared in.
You will not be able to close the modal from inside the child component without passing a method from the parent component that changes the state that is passed down.
So in order to close your modal, you'll need to create a method this.closeModal and pass it from the Parent to the child...
// Parent
closeModal = () => {
this.setState({showModal: false});
}
// ...
<ChildModal show={this.state.showModal} handleClose={this.closeModal} />
// ...

When you define a state in a parent component, pretty much everything around that state is handled from there. The child component can only receive data from the parent component in the form of props.
The parent component controls the opening and closing state of the modal so for you to be able to close the modal from the child component, you have to define a function closeModal on the parent component which will set the showModal variable (defined in the state of your parent component) from true back to false.
closeModal = () => {
this.setState({ showModal: false });
}
Then pass this function as props to the child component and call the function from there. When you click on the close button on the modal, the state will be updated on the parent component.

class Parent extends Component {
constructor(props) {
super(props);
this.showModal = this.showModal.bind(this);
this.closeModal = this.closeModal.bind(this)
this.state = {
showModal: false
};
}
showModal() {
this.setState({ showModal: true });
}
closeModal() {
this.setState({ showModal: false });
}
renderRow() {
return (
<tr>
<td onClick={() => this.setState({ show: true })}>test</td>
<ChildModal show={this.state.showModal} close={this.state.closeModal}/>
</tr>
);
}
}
class ChildModal extends Component {
render() {
return (
<Modal show={this.props.showModal}>
<Modal.Header closeButton>
<Modal.Title>Test</Modal.Title>
</Modal.Header>
<Modal.Body>
<buttom onClick={this.props.closeModal()}> ......
</Modal.Body>
</Modal>
);
}
}

Related

React Native - Using Props for Modal

I am trying to use modal on my application but I want to separated into different classes as App.js and /components/modal. The problem I encountered is I couldn't pass the props properly. Here is my codes.
I imported modal.
import InfoModal from '../components/InfoModal';
I created state.
state = {modalVisible: false}
The visible function on press.
setModalVisible = (visible) => {
this.setState({ modalVisible: visible });
}
Calling component.
render() {
const { modalVisible } = this.state;
return (
<InfoModal visible= {modalVisible} />
<TouchableOpacity onPress={() => this.setModalVisible(true)} ><Text style={styles.infoButton}>Info</Text></TouchableOpacity>
)}
I didn't understand what prop should I set and how, to work it properly.
Since you have a render method, I assume your App.js is a Class component.
You can create a state on the constructor like that
class App extends React.Component {
constructor(props){
super(props);
this.state = {
modalVisible: false,
}
}
// rest of the class
}
Your function for changing the modal's visibility should be like that
setModalVisible = (visible) => {
this.setState({modalVisible: visible});
}
That's how you control the state on the App class.
And for your modal, you pass App.state.modalVisible as a prop.
<InfoModal visible={this.state.modalVisible} />
When you use setState, all the components receiving that new value as a prop will properly update
use state and method inside Modal component and toggle it by using modal reference.
Put
const { modalVisible } = this.state;
and
setModalVisible = (visible) => {
this.setState({ modalVisible: visible });
}
in InfoModal.js
then use it like this
render() {
return (
<InfoModal ref={(c)= this.infoModal=c }visible= {modalVisible} />
<TouchableOpacity onPress={() => this.infoModal.setModalVisible(true)} ><Text style={styles.infoButton}>Info</Text></TouchableOpacity>
)

Calling Child component function from Parent component in React.js

I am trying to call a function in my child component from a button click event in my parent component.
Parent Component:
class Parent extends Component{
constructor(props){
super(props);
this.state = {
//..
}
}
handleSaveDialog = (handleSaveClick) => {
this.handleSaveClick = handleSaveClick;
}
render(){
return(
<div>
<Button onClick={this.openDialog}>Open Dialog</Button>
<Dialog>
<DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
<DialogContent>
<Child handleSaveData={this.handleSaveDialog}/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleSaveClick} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
In the above code, the Parent component renders a Child component modal dialog (based on Material-UI) on click of a button. The Save button, part of the Dialog component in Parent, when clicked should call a save function in the Child component. As you can see, I have passed a callback function handleSaveDialog through the Childcomponent props named handleSaveData. The save button click will call handleSaveClick on the child, once the Child component mounts and passes the callback to the Parent component.
Child Component:
class Child extends Component{
constructor(props){
super(props);
this.state = {
//..
}
}
componentDidMount(){
console.log('mount');
this.props.handleSaveData( () => this.handleSaveClick());
}
handleSaveClick = () => {
console.log('save clicked');
}
render(){
return(
<div>
//..
</div>
);
}
}
In the above code, I am using the accessing the callback function passed by the Parent component props and binding it to the Child component's save fucntion handleSaveClick.
Problem:
When I click the Open Dialog button in Parent, for the first time, the Dialog mounts the Child component. However, the click on Save button does not work (no error). After, closing the dialog, when I reopen the Dialog and click on Save, the handleSaveClick in the Child dialog is triggered and a message is logged in the browser console. Any idea why this works on the second time and not the first time?
Remember, the Child Component is mounted/loaded only when I click the Open Dialog on the Parent component.
References:
https://material-ui.com/components/dialogs/#form-dialogs
Call child method from parent
https://github.com/kriasoft/react-starter-kit/issues/909#issuecomment-390556015
It will not work because if you console log this.handleSaveClick in render function it will be undefined as there is no re-render. So there are 2 ways to go for this:
Saving function in the state. Link: https://codesandbox.io/s/fervent-browser-gw75c
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
openDialog = () => {
this.setState(preState => ({
open: !preState.open
}));
};
handleSaveDialog = handleSaveRef => {
this.setState({
handleSaveClick: handleSaveRef
});
};
render() {
console.log("Render", this.handleSaveClick);
return (
<div>
<Button onClick={this.openDialog}>Open Dialog</Button>
<Dialog open={this.state.open}>
<DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
<DialogContent>
<Child handleSaveData={this.handleSaveDialog} />
</DialogContent>
<DialogActions>
<Button onClick={this.state.handleSaveClick} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
</div>
);
}
class Child extends Component {
componentDidMount() {
console.log("mount");
this.props.handleSaveData(this.handleSaveClick);
}
handleSaveClick = () => {
console.log("save clicked");
};
render() {
return <div>//..</div>;
}
}
Using ref. Link: https://codesandbox.io/s/musing-kalam-nj29n
const childRef = React.createRef();
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
openDialog = () => {
this.setState(preState => ({
open: !preState.open
}));
};
handleSaveClick = () => {
if (childRef.current) {
childRef.current.handleSaveClick();
}
};
render() {
return (
<div>
<Button onClick={this.openDialog}>Open Dialog</Button>
<Dialog open={this.state.open}>
<DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
<DialogContent>
<Child ref={childRef} />
</DialogContent>
<DialogActions>
<Button onClick={this.handleSaveClick} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
class Child extends Component {
handleSaveClick = () => {
console.log("save clicked");
};
render() {
return <div>//..</div>;
}
}
Using callback to save instance and than use arrow function:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
openDialog = () => {
this.setState(preState => ({
open: !preState.open
}));
};
handleSaveDialog = handleSaveRef => {
this.handleSaveClick = handleSaveRef;
};
render() {
return (
<div>
<Button onClick={this.openDialog}>Open Dialog</Button>
<Dialog open={this.state.open}>
<DialogTitle id="form-dialog-title">Child Dialog</DialogTitle>
<DialogContent>
<Child handleSaveData={this.handleSaveDialog} />
</DialogContent>
<DialogActions>
<Button onClick={() => this.handleSaveClick()} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
class Child extends Component {
componentDidMount() {
console.log("mount");
this.props.handleSaveData(this.handleSaveClick);
}
handleSaveClick = () => {
console.log("save clicked");
};
render() {
return <div>//..</div>;
}
}
You will need to use arrow function in onClick as it will create a new function every time we click and thus getting a new instance of handleClick. And if you pass this.handleClick it won't work because it is undefined. You can check this by logging the value of this.handleClick in render function.
Note: Use the 2 option its more reliable.
Hope this helps!
Well, I don't know why you have this sort of scenario but the first things that comes in my mind is that you can write handleSaveClick method in your Parent component. And in case you need to use that function on any action that might be happening in your child component, you can pass this function as a prop from parent component. So your both cases can be handled
You can call this method on something happening in parent component
You can use the same method on any action that is happening in your child component.
If still you think that you have to define the method in your child component, you can use the refs

Modal element is not hiding [React & React - Bootstrap]

I've created modal with React Bootstrap. Despite the fact, that I'm using onHide function, nothing happens. Here's my modal:
<React.Fragment>
<Modal
{...this.props}
bsSize="large"
aria-labelledby="contained-modal-title-lg"
show={this.state.showModal}
onHide={this.handleHide}
>
<Modal.Body>
...
</Modal.Body>
<Modal.Footer>
<Button id = "closeModal" variant="danger" onClick=
{this.handleHide.bind(this)}>
Save and close
</Button>
</Modal.Footer>
</Modal>
</React.Fragment>
I'm passing "show" from other component, when click occurs on some element. onClick on that element is specified to: "showModal: true". Then I'm passing showModal to other component that is rendering different elements according to option choosed:
{this.state.addingState && (
<MyForm
{...this.state.item}
show={this.state.showModal}
...
/>
)}
At last in MyForm component I have function that passes props to component with modal:
createModalComponent {
...
let modalComponentProps= {
...
show: this.props.show,
}
So, this is the way "show" is going. In my file with modal component I have function for handling hide:
handleHide() {
this.setState({ showModal: false });
}
Now in this component showModal is initialize in state like so:
constructor(props) {
super(props);
this.state = {
showModal: this.props.show
};
this.handleHide = this.handleHide.bind(this);
}
I've tried many things. Other state variables, without initializing showModal in state and many more. When clicking on the button or beyond the modal, modal is still visible and not hiding. I will be very grateful for your help and/or suggestions how to fix this.
So, the way showModal is going: parent component (where this.state.addingState is happening) -> MyForm component (where let modalComponentProps= { show: this.props.show, ... happens) -> actual modal component
Code on CodePen
you have 2 possibilities: you can add the method to your parent and pass the method + the result of show like this (use same name of props and method for best practice, so you will be not confuse):
Parent
<Modal
{...this.props}
bsSize="large"
aria-labelledby="contained-modal-title-lg"
show={this.state.showModal}
handleHide={this.handleHide}
>
Child
<MyForm
show={this.props.showModal}
handleHide={this.props.handleHide}
/>
To use the modal from parent, you can call it like this in child: this.props.handleHide(true).
Or you let the child manage the state by itself, so you would place the method and state in child, not in parent (depending on the architecture).
It is not recommended to add the props in child state.
Also, you could use es6 function to avoid binding like this:
handleHide = () => this.setState({ showModal: false });
Look on the shouldComponentUpdate method
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
You are checking only props but you are changing the state of the component. Fix the method or remove it and it will be working

Tie an action to a state change in pure React

Learning React so might be a bit nooby question. Consider this code:
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle(on) {
this.setState({
isOn: on
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: false
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
});
this.props.handleToggle(on);
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
ReactDOM.render(<Application />, document.getElementById('app'));
As you can see currently when the button inside the Container is clicked handleButtonToggle() is fired which changes the state of the button itself and then calls a function to change the state of the parent Container. Is this the React way to do it? At the moment Container state is changed when the function handleButtonToggle is fired. Ideally I would want Container state to be dependent on MyButton state directly (cuz maybe in future there will be ways to set button state other than through handleButtonToggle, and I don't want to manually call this.props.handleToggle every time the state changes.). In other words is there a way to do something like this.props.handleToggle(this.state.pressed) in the button component when its state changes.
Codepen
After reviewing the code, a better way to write the Button component is to make it a controlled component. If the container component requires to maintain the pressed state, a controlled button component would receive the pressed state from the container component as props.
<MyButton pressed={this.state.pressed} onToggle={this.handleToggle} />
And the render method of the Button component should be:
render() {
return (
<div>
<button onClick={this.props.onToggle}>
{this.props.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
The actual button toggle will be done in the handleToggle method of the container component:
handleButtonToggle() {
let { pressed } = this.state;
pressed = !pressed;
this.setState({
pressed
});
}
You can either pass a callback as the second argument to setState or implement componentDidUpdate on your component to wait for state changes. The smallest change would be the former:
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
}, () => {
this.props.handleToggle(on);
});
}
or with componentDidUpdate:
componentDidUpdate(prevProps, prevState) {
if (prevState.on !== this.state.on) {
this.props.handleToggle(this.state.on);
}
}
Here is a working codepen: https://codepen.io/damien-monni/pen/XRwewV.
I tried to keep as much as I can of your code so you can focus on really needed changes.
You need to create a controlled component. The state of your button will be stored in the container and pass by props to the child MyButton component.
/*
* A simple React component
*/
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle() {
this.setState({
isOn: !this.state.isOn
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton pressed={this.state.isOn} handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: this.props.pressed
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
this.props.handleToggle();
}
componentWillReceiveProps(nextProps) {
if (nextProps.pressed !== this.props.pressed) {
this.setState({ pressed: nextProps.pressed });
}
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById('app'));
You need to do it because you want to share a state between two of your components. This is documented here.
I let you look at the codepen and ask your questions about it. ;)

Why does State remains unchanged after setState

I am trying to show/hide a modal when a user clicks on a item inside a list. The modal shows up as planned but cannot be hidden. When _dismiss() is called, setState executes but when I console.log the state inside the callback, the parameter show is still true.
Why is this happening?
Message.jsx
export default class Message extends React.Component {
constructor(props) {
super(props);
this.state = {
show: false
};
this._onClick = this._onClick.bind(this);
this._dismiss = this._dismiss.bind(this);
}
_onClick(e) {
e.preventDefault();
this.setState({
show: true
})
}
_dismiss() {
this.setState({
show: false
}, function () {
console.log(this.state) // logs: Object {show: true}
})
}
render() {
return (
<div onClick={this._onClick} className="message">
<Modal show={this.state.show} close={this._dismiss}>
<h1>...</h1>
</Modal>
</div>
);
}
}
Modal.jsx
export default class Modal extends React.Component {
constructor(props) {
super(props);
this._onClose = this._onClose.bind(this);
}
_onClose() {
this.props.close()
}
render() {
if (this.props.show) {
return (
<div className="modal" onClick={this._onClose} >
<div className="content">
{this.props.children}
</div>
</div>
);
} else {
return null
}
}
}
The div is still getting on onClick events event when its children are clicked. I suspect _dismiss is being called and then _onClick is being called. React batches setState calls so it ends up setting show back to true.
Remedies.
If the close callback of handler give you the event as an argument. Call e.stopPropagation() or From #richsilv in the comments e.stopImmediatePropagation().
Or if it doesn't pass the event. Check in the _onClick if show is true or false. If it is true, don't setState

Categories

Resources