Closing React Semantic UI modal with button and close icon - javascript

I have a Modal where the user needs to fill in some forms and save whatever was filled in through a button in the Modal. When the user saves I would like the modal to close. I can get this done, through using the open prop on the Modal component. But if I do this, the modal doesn't close when I attempt to do so through the closeIcon.
What can I do to allow the user to close the Modal through both methods?
Here is my current modal code:
handleCreateButton (evt) {
evt.preventDefault()
// ...
// code to save whatever was typed in the form
// ...
this.setState({showModal: false})
}
renderModalForm () {
const {
something,
showModal
} = this.state
// if I have the open props, I get to close the Modal after the button is clicked
// however, when using the icon or clicking on dimmer it wont work anymore.
return (
<Modal closeIcon closeOnDimmerClick open={showModal} trigger={<Button onClick={() => this.setState({showModal: true})}><Icon className='plus'/>New Challenge</Button>}>
<Modal.Header>My Modal</Modal.Header>
<Modal.Content>
<Form>
<Form.Input
label='Something'
value={something}
onChange={(evt) => this.handleChangeForms('something', evt.target.value)}
/>
<Button onClick={(evt) => this.handleCreateButton(evt)}>Save</Button>
</Form>
</Modal.Content>
</Modal>
)
}

when you use the open prop you need to use the onClose handler prop as well.
By the way, closeOnDimmerClick is set to true by default.
Here is a running example:
const { Modal, Form, Button, Icon } = semanticUIReact;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
something: '',
showModal: false
}
}
handleChangeForms = (e, { value }) => {
this.setState({ something: value });
}
handleCreateButton(evt) {
evt.preventDefault()
this.closeModal();
}
closeModal = () => {
this.setState({ showModal: false })
}
render() {
const {
something,
showModal
} = this.state
return (
<Modal closeIcon onClose={this.closeModal} open={showModal} trigger={<Button onClick={() => this.setState({ showModal: true })}><Icon className='plus' />New Challenge</Button>}>
<Modal.Header>My Modal</Modal.Header>
<Modal.Content>
<Form>
<Form.Input
label='Something'
value={something}
onChange={this.handleChangeForms}
/>
<Button onClick={(evt) => this.handleCreateButton(evt)}>Save</Button>
</Form>
</Modal.Content>
</Modal>
)
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.9/semantic.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/semantic-ui-react#0.77.1/dist/umd/semantic-ui-react.min.js"></script>
<div id="root"></div>

Related

Is there a way I can close Modal without using the default Buttons on ANTD?

So I am new to ReactJS and I'm using ANT Design and currently playing around with their Modal. I want to know if we can close the Modal without using the OK and Cancel buttons.
So I removed these buttons. And created a Button inside the config. I want to close the Modal using that Button. Any help would be great! Thanks in advance!
Here is my code.
const { Modal, Button } = antd;
const ReachableContext = React.createContext();
const UnreachableContext = React.createContext();
const handleButtonOnClick = () => {
console.log('this button was clicked');
}
const config = {
visible: false,
title: 'Use Hook!', icon: null,
okButtonProps: { style: { display: 'none' } },
// cancelButtonProps: { style: { display: 'none' } },
content: (
<div>
<ReachableContext.Consumer>
{sample => (
<Button
type='primary'
block
>
Click Me Button
// IS THERE A FUNCTION THAT I CAN CLOSE THE MODAL USING THIS BUTTON?
</Button>
)}
</ReachableContext.Consumer>
</div>
),
};
const App = () => {
const [modal, contextHolder] = Modal.useModal();
return (
<ReachableContext.Provider value={modal}>
<Button
onClick={() => {
modal.confirm(config);
}}
>
Confirm
</Button>
{contextHolder}
</ReachableContext.Provider>
);
};
ReactDOM.render(<App />, mountNode);
This is how I close/show the Modal. I don't use Ok or cancel button. If the prop showForm is true then Modal will show up otherwise not.
import React, { Component } from "react";
import { connect } from "react-redux";
import * as actions from "../../actions";
import { Modal, Icon } from "antd";
class FormContainerModal extends Component {
state = {};
render() {
const { showForm } = this.props;
return (
<>
<Modal
title={
<div>
Title
</div>
}
destroyOnClose={true}
footer={null}
centered
maskClosable={false}
onCancel={this.props.closeModal}
visible={showForm} //it will close the modal if showForm is false
width="950px"
>
<div>
My Content
</div>
</Modal>
</>
);
}
}
const mapStateToProps = state => {
return {
showForm: state.form.showForm
};
};
export default connect(mapStateToProps, actions)(FormContainerModal);
In your case, you can change the boolean value of showForm upon button click.
<Button
type='primary'
block
onClick={()=>this.setState({showForm: false})} //here make showForm to false to close the modal
>
Close the Modal
</Button>
If you know you only have one modal open (or don't mind also closing any additional open modals), you can call the class method
Modal.destroyAll()
from anywhere and it will do the trick.
You can trigger the destruction of the modal using a button within the modal content like so:
const modal = Modal.info();
const closeModal = () => modal.destroy();
modal.update({
title: 'Updated title',
content: (
<Button onClick={closeModal}>Destroy</Button>
),
});

I have my modal component in a different file and I'm trying to open it from another component

BuySectionItem.js
class BuySectionItem extends React.PureComponent {
constructor( props ) {
super( props );
this.state = {
modalIsOpen: false,
}
this.toggleTicketModal = this.toggleTicketModal.bind( this );
}
toggleTicketModal = () => {
this.setState( { modalIsOpen: ! this.state.modalIsOpen } );
}
componentDidMount() {
// this.setActivePrice();
}
outputBuyButton( offer ) {
// Universe ID override is present.
return <Button text="Buy Ticket" type="green-gradient" onClick={ this.toggleTicketModal }/>
return null;
}
render() {
<div>
{this.state.modalIsOpen &&
<TicketModal isOpen={this.state.modalIsOpen} toggleTicketModal={ this.toggleTicketModal } />
}
</div>;
}
}
export default BuySectionItem;
TicketModal.js
import React from 'react';
import Modal from 'react-modal';
import PropTypes from 'prop-types';
const customStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
},
};
Modal.setAppElement( 'body' )
class TicketModal extends React.Component {
componentDidMount() {
this.handleKeyEvents();
}
componentWillUnmount() {
this.removeKeyHandler();
}
/**
* Watch for the escape key when the modal is open to allow users to escape from the modal.
*/
handleKeyEvents() {
const handleKeyDown = event => {
if ( event.keyCode === 27 ) {
this.props.toggleTicketModal( event );
}
};
/*
* Attach our event listener to the window.
*/
window.addEventListener( 'keydown', handleKeyDown );
/*
* Cancel the key event handling, and remove
*/
this.removeKeyHandler = () => {
window.removeEventListener( 'keydown', handleKeyDown );
};
}
render() {
const {
isOpen,
// pending,
toggleTicketModal,
} = this.props;
return (
<Modal
isOpen={isOpen}
onRequestClose={toggleTicketModal()}
style={customStyles}
contentLabel="Meal Modal"
>
<div className="modal-wrapper">
<div className="container text-center">
<h1>Hello</h1>
<h2>ID of this modal is 100</h2>
<h3>This is an awesome modal.</h3>
<button onClick={toggleTicketModal()}>close</button>
</div>
</div>
</Modal>
)
}
}
TicketModal.propTypes = {
pending: PropTypes.bool,
toggleTicketModal: PropTypes.func,
isOpen: PropTypes.bool,
};
export default TicketModal;
As you can see I am trying to open the ticket modal component from the `buysectionitem` component on a button Click.
But for some reason the Modal doesn't seem to be opening.
When I kept a break point I see that the `togglemodal` function is being called but doesn't open at all.
I added some more code as I need guidance with the handle key events as well. This key event which was supposed to remove the modal from the screen when I click escape which doesn't seem to be working.
Optimized code
class BuySectionItem extends React.Component {
constructor() {
super();
this.state = {
showModal: false
};
}
handleOpenClose = () => {
this.setState(prev => ({ showModal: !prev.showModal }));
};
render() {
return (
<div>
<button onClick={this.handleOpenClose}>Trigger Modal</button>
<Modal
isOpen={this.state.showModal}
contentLabel="Minimal Modal Example"
>
<TicketModal handleOpenClose={this.handleOpenClose} />
</Modal>
</div>
);
}
}
class TicketModal extends React.Component {
render() {
const { handleOpenClose } = this.props;
return (
<div>
<button onClick={handleOpenClose}>Close Modal</button>
<hr />
<p>Welcome to opened model</p>
</div>
);
}
}
Live demo
you are calling the toggleTicketModal function when the TicketModal renders,call it like this
<button onClick={()=>toggleTicketModal()}>close</button>
this will do.
there are many mistakes in your code.
button onclick handler in modal should be like following,
<button onClick={toggleTicketModal}>close</button>
also you don't have to do same handling while closing the modal, following is redundant inside Modal props
onRequestClose={toggleTicketModal}
also main container in your case BuySectionItem.js has few issues.
1.there is no return in render method.
2.conditional rendering is not done correctly, you are never calling outPutBuyButton based on modalIsOpen state
3. you have used name "isModalOpen" instead of "modalIsOPen" , bad typo.
here's working code. modify as per your needs.
https://codesandbox.io/s/gracious-hopper-vmerd

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

reuse Modals in React

I have a very simple modal that I want to reuse to display dynamic data
class Modal extends Component {
constructor(props) {
super(props)
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
}
handleKeyDown(event) {
if (event.key === 'Escape') {
this.handleCloseModal()
}
}
handleCloseModal() {
// What should I do here? Unmount?
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown, false);
}
render() {
return(
<div className="modal">
<button onClick={() => this.handleCloseModal()}>close modal</button>
{this.props.children}
</div>
)
}
}
export default Modal
I would like to open the modal with the data from elem.description for each elem in the records array.
{records.map(function(elem, index) {
return <button
key={index}
onClick={// Something like <Modal>{elem.description}</Modal>}
/> open modal for {elem.name}</button>
})}
I read about some implementations that will do something like
<Modal show=true>...</modal>
and toggle the visibility of it. can i Just Unmount the component? is that a a good practice?
One way of doing that is to save the clicked element in the state and then, when it is clicked display the modal.
{records.map(function(item, index) {
return <button
key={index}
onClick={this.openModal(item)} // open modal and set the current item in the state
/> open modal for {elem.name}</button>
})}
You need a function to set the element as current and open the modal
openModal = (item) =>() => {
this.setState({currentItem:item, isModalVisible: true})
}
Finally in your modal just pass an item component as chidlren and give it data coming from the state
<Modal isVisible={this.state.isModalVisible}>
<MyItem data={this.state.currentItem} />
</Modal>

setState() does not change state when called from element that depends on state to render

I have a little component like this (Code below is simplified to the parts needed) that behaves very strange when it comes to updating the state.
class Componenent extends React.Component {
constructor(props) {
super(props);
this.state = {showStuff: false};
}
render() {
return(
//Markup
{this.state.showStuff && (
<button onClick={() => this.setState({showStuff: false})} />
)}
// More Markup
);
}
}
The state gets updated somewhere else in the component, so the prop is true when the button is clicked.
A click also triggers the setState function (callback gets executed), however the state does not update.
My guess is that it does not update because the function is called by an element that directly depends on the state prop to be visible.
I figured out that adding another prop test: true to the state and changing that property to false when the button is clicked also triggers the showStuff prop to change to false. So it works when I make strange hacks.
Can someone explain this weird behavior to me? I can't gasp why the above snippet does not work like intended.
Here is the entire component:
class ElementAdd extends React.Component {
constructor(props) {
super(props);
this.defaultState = {
showElementWheel: false,
test: true
};
this.state = this.defaultState;
}
handleAddCardClick() {
if (this.props.onCardAdd) {
this.props.onCardAdd({
type: ElementTypes.card,
position: this.props.index
});
}
}
handleAddKnowledgeClick() {
if (this.props.onCardAdd) {
this.props.onCardAdd({
type: ElementTypes.knowledge,
position: this.props.index
});
}
}
handleTabPress(e) {
if (e.key === 'Tab') {
e.preventDefault();
let target = null;
if (e.shiftKey) {
if (e.target.previousSibling) {
target = e.target.previousSibling;
} else {
target = e.target.nextSibling;
}
} else {
if (e.target.nextSibling) {
target = e.target.nextSibling;
} else {
target = e.target.previousSibling;
}
}
target.focus();
}
}
hideElementWheel() {
// This is somehow the only option to trigger the showElementWheel
this.setState({ test: false });
}
render() {
return (
<div
className="element-add"
style={{ opacity: this.props.invisible ? 0 : 1 }}
onClick={() => this.setState(prevSate => ({ showElementWheel: !prevSate.showElementWheel }))}
>
<PlusIcon className="element-add__icon" />
{this.state.showElementWheel && (
<React.Fragment>
<div className="element-add__wheel">
<button
autoFocus
className="element-add__circle"
onClick={this.handleAddCardClick.bind(this)}
onKeyDown={this.handleTabPress.bind(this)}
title="New element"
>
<ViewModuleIcon className="element-add__element-icon" />
</button>
<button
className="element-add__circle"
onClick={this.handleAddKnowledgeClick.bind(this)}
onKeyDown={this.handleTabPress.bind(this)}
title="New knowledge-element"
>
<FileIcon className="element-add__element-icon" />
</button>
</div>
<div
className="element-add__close-layer"
onClick={() => {
this.hideElementWheel();
}}
/>
</React.Fragment>
)}
</div>
);
}
}
By writing onClick={this.setState({showStuff: false})} you are actually calling setState as soon as your button is rendered.
You want to give a function reference to onClick, not call it immediately on render.
<button onClick={() => this.setState({showStuff: false})} />
If your button is inside another element with a click listener that you don't want to run on the same click, you must make sure that the click event doesn't propagate to the parent.
<button
onClick={(event) => {
event.stopPropagation();
this.setState({showStuff: false});
}}
/>
Actually the onClick prop expects a function, you are already providing a function call, so the setState will be called each time the component is rendered, not when clicked.
Try this:
<button onClick={() => this.setState({showStuff: false})} />
Should behave as you expect :)
Works perfectly fine when I update showStuff true (see updated code below.). My guess is the code that is supposed to set showStuff: true is not working. I also added some text in the button.
import React from 'react'
import ReactDOM from 'react-dom'
class Componenent extends React.Component {
constructor(props) {
super(props);
this.state = {showStuff: true};
}
render() {
return(
<div>
{this.state.showStuff && (
<button onClick={() => this.setState({showStuff: false})} > This is a button</button>
)}
</div>
);
}
}
ReactDOM.render(<Componenent />,
document.getElementById('root')
);
Before clicking
After clicking

Categories

Resources