Reusable Modal Component React Typescript - reactjs

I have a component which has a button within it, like so -
<Button variant="primary" disabled={checkAccepted} onClick={openModal}>Send</Button>
I would like this button to, when it is active, to open up a modal when clicked. I am unsure how to do this and have been messing around with props but can't seem to figure it out. I also want the modal to be reusable so that any content can be passed in the modal body.I am thinking how do I open up the modal from within my openModal function?
I tried returning it like so -
const openModal = () => {
return (
<Modal>
<ModalBody>*Pass in swappable content here*</ModalBody>
</Modal>
)
}
But that doesn't seem to work. I am sure I am missing something.

You can't return components from an event handler. The way to handle events in react is almost always to alter the state of your application which triggers a re-render. In your case you need to keep track of the open state of your modal.
This can be done either in a controlled way (you keep track of the open state yourself and pass it to your <Modal> component as a prop) or in an uncontrolled way (the <Modal> component manages the open state itself). The second approach requires that you provide e.g. an element to render to your Modal component that acts as a trigger:
const MyModal = ({ children, trigger }) => {
const [modal, setModal] = useState(false);
const toggle = () => setModal(!modal);
return (
<div>
{React.cloneElement(trigger, { onClick: toggle })}
<Modal isOpen={modal} toggle={toggle}>
<ModalBody>{children}</ModalBody>
</Modal>
</div>
);
};
Then you can use it like that:
<MyModal trigger={<Button variant="primary">Send</Button>}>
<p>This is the content.</p>
</MyModal>
Or you can implement it in a controlled way. This is more flexible as it allows you to render the triggering element anywhere:
const MyModal = ({ children, isOpen, toggle }) => (
<div>
<Modal isOpen={isOpen} toggle={toggle}>
<ModalBody>{children}</ModalBody>
</Modal>
</div>
);
Usage Example:
function App() {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
return (
<div className="App">
<Button variant="primary" onClick={toggle}>
Send
</Button>
<MyModal isOpen={isOpen} toggle={toggle}>
<p>This is the content.</p>
</MyModal>
</div>
);
}

You should pass the function which triggers the modal to your <Button /> component as prop. Then, in your component, you want to add the onClick event. You can't set an onClick event to the <Button />. It will think of onClick as a prop being passed to <Button />. Within <Button /> you can set the onClick event to an actual <button> element, and use the function which was passed in as a prop on that event.
You can use state to keep track of when the modal button is clicked. Your function can look like: (I am using class based components here, but you can do the same thing with functional components)
buttonClickedHandler = () => {
this.setState({isModalButtonClicked: !this.state.isModalButtonClicked});
}
Then, you can set the Modal component,
<Modal isShow={this.state.isModalButtonClicked} modalButton={this.buttonClickedHandler}>
<div> ...set contents of modal</div>
</Modal>
<button onClick={this.buttonClickedHandler}>Show Modal</button>
So, within the Modal component, you can have something like this:
<React.Fragment>
<Backdrop showModal={this.props.isShow} clicked={this.props.modalButton}/>
{this.props.children}
</React.Fragment>
Backdrop is basically the greyed out background. You can also set an onClick event to listen to when the backdrop is clicked.

Related

mui Dialog not closing properly

I have a problem with a dialog not closing properly.
Dialog open is set to a state variable intialized as true, and immediately changed to false.
The dialog doesn't close properly, and the app does not respond.
I have demonstrated this in the following sandbox where the button can not be clicked, though it should be available.
EDIT:
To make clear. The dialog is not supposed to ever open. But the button outside the dialog (with the text "a button") should be clickable.
https://codesandbox.io/s/adoring-benji-300mcm
This code will work:
export default function App() {
const [open, setOpen] = useState(false);
console.log(open);
return (
<div>
<Dialog open={open} height="100px" width="100px" id="111">
<button onClick={() => setOpen(false)}>close</button>
</Dialog>
<button onClick={() => console.log("click")}>a button</button>
</div>
);
}
You don't need the useEffect to change state immediately, you could just pass false as a desired initial value to the state. Now the button "a button" is working.
EDIT:
To make the code working without removing useEffect, add conditional rendering:
export default function App() {
const [open, setOpen] = useState(true);
useEffect(() => setOpen(false), []);
console.log(open);
return (
<div>
{open && (
<Dialog open={open} height="100px" width="100px" id="111">
<button onClick={() => setOpen(false)}>close</button>
</Dialog>
)}
<button onClick={() => console.log("click")}>a button</button>
</div>
);
}
My best guess is that in your code the Dialogue gets rendered anyways, even though it is not displayed, because at first "open" was true. So in order for it not to be rendered, or to be rendered conditionally, you should check state.

Nested Modal Popups React or any other

Need modal inside modal if you click on edit then is one modal is opening. I need one more modal inside that opened modal
To create nested modal popups in React you can use a combination of modal components and conditional rendering.
Example
import React from 'react';
const ParentModal = () => {
const [showChildModal, setShowChildModal] = React.useState(false);
return (
<>
<button onClick={() => setShowChildModal(true)}>Show Child Modal</button>
{showChildModal && <ChildModal onClose={() => setShowChildModal(false)} />}
</>
);
};
const ChildModal = ({ onClose }) => {
const [showGrandchildModal, setShowGrandchildModal] = React.useState(false);
return (
<>
<button onClick={() => setShowGrandchildModal(true)}>Show Grandchild Modal</button>
{showGrandchildModal && <GrandchildModal onClose={() => setShowGrandchildModal(false)} />}
<button onClick={onClose}>Close</button>
</>
);
};
const GrandchildModal = ({ onClose }) => (
<>
<p>I'm the grandchild modal!</p>
<button onClick={onClose}>Close</button>
</>
);
export default ParentModal;
In the above example, we have three modal components: ParentModal, ChildModal, and GrandchildModal. The ParentModal component is the root of the nested modal popups, and it contains a button that will show the ChildModal when clicked. The ChildModal component, in turn, contains a button that will show the GrandchildModal when clicked. Each modal component also has a "Close" button that will close the modal when clicked.
We use the useState hook in each component to track the state of the modal (whether it is open or closed), and we use conditional rendering

Close antd popover and open a child antd modal in the same function

I have an Antd popover, that by clicking a button inside its content, opens a modal.
I want to close the popover when the modal opens.
When I tried just passing the popover visibility state setter down to the modal as a prop, there was a problem. There was some kind of "collision" between the state of the modal and the passed down prop state of the popover:
Collision CodeSandbox example
I did find a workaround - creating the modal state variables in the parent component (the popover) and passing them down to the modal using props:
Working CodeSandbox example
First of all, you can notice that the modal isn't closing at it supposed to - there's no nice smooth animation minimizing it, it just suddenly disappears. For reference, you can look here to see how it should look like when closing.
So my question is - why did this collision happen? Is there a better way to solve it?
Thanks!
This collision happens because in show modal handler you set visibility of popover to false and hide it and ant-popover-hidden class add to it's div element so anything inside it would not display like Modal however you show modal but because of its parent it couldn't visible, so I think You must separate modal from the popover content and place it somewhere beside them like this:
const Test = () => {
const [isSharePopoverVisible, setIsSharePopoverVisible] = useState(false);
const [isModalVisible, setIsModalVisible] = useState(false);
const handlePopoverVisibleChange = () => {
setIsSharePopoverVisible(!isSharePopoverVisible);
};
const handleOk = () => {
setIsModalVisible(false);
};
const handleCancel = () => {
setIsModalVisible(false);
};
const showModal = () => {
setIsModalVisible(true);
setIsSharePopoverVisible(false);
};
return (
<>
<Popover
trigger="click"
title="Test"
visible={isSharePopoverVisible}
onVisibleChange={handlePopoverVisibleChange}
content={
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
}
>
<Button>Test</Button>
</Popover>
<Modal
title="Basic Modal"
visible={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
</Modal>
</>
);
};

Office Fluent UI / Fabric UI Modal - how can I close it from the body component?

I'm using the Modal component from the fluent-ui-react
https://developer.microsoft.com/en-us/fluentui#/controls/web/modal
like this:
function ModalExtended(props) {
const [isModalOpen, { setTrue: showModal, setFalse: hideModal }] = useBoolean(
false
);
const isDraggable = useBoolean(false);
const titleId = useId("title");
return (
<div>
<DefaultButton onClick={showModal} text={props.buttonText} />
<Modal
titleAriaId={titleId}
isOpen={isModalOpen}
onDismiss={hideModal}
isBlocking={false}
containerClassName={contentStyles.container}
>
<div className={contentStyles.header}>
<span id={titleId}>{props.gridHeader}</span>
<IconButton
styles={iconButtonStyles}
iconProps={cancelIcon}
ariaLabel="Close popup modal"
onClick={hideModal}
/>
</div>
<div className={contentStyles.body}>{props.body}</div>
</Modal>
</div>
);
}
Then i call the ModalExtended component from other components like this:
<ModalExtended
buttonText="Open modal button text"
gridHeader="Modal header text"
body={
<GenericTreeGridContainer/>
}
/>
In the body prop i send another component (GenericTreeGridContainer) that i would like to render when the Modal opens.
In this body component i have a click event which, when it finishes its work should also close the Modal.
Is there a way to pass the hideModal function from the ModalExtended components to my body component so i can close the Modal from the body component?
Define a parent component with isModalOpen and hideModal, and pass them into both the modal and body as props. You also might be able to render the {props.body} instead like <props.body hideModal={...} /> but I haven't tried that to see how good of a pattern it is.

How do I close a dialog in another component from Material-UI in React?

I have a modal that have a component inside.
I want to close the modal from that component.
I got a component that have:
<Modal open={modalClients} onClose={handleCloseModalClients}>
<div className={classes.paper}>
<ClientsTable />
</div>
</Modal>
It is possible to close the modal inside ClientsTable?
It looks like handleCloseModalClients is what closes the modal, so you should just need to pass it into ClientsTable and call it somehow. For example, if you have a button in ClientsTable:
<Modal open={modalClients} onClose={handleCloseModalClients}>
<div className={classes.paper}>
<ClientsTable onCloseModal={handleCloseModalClients} />
</div>
</Modal>
const ClientsTable = (props) => (
// Not sure what the inside of ClientsTable looks like, but
// it should have something you can attach the handler to:
<div>
<button onClick={props.onCloseModal}>Close Modal</button>
</div>
)
According to https://material-ui.com/api/modal/ , you can control opening and closing the modal through the open prop in the component.
Therefore, you can define a state variable in your modal component
state = { open: false }
and a function to close it closeModal = () => { this.setState({ open: false }); }
and you can pass closeModal function as a prop to the child component inside the modal
<ClientsTable closeModal={closeModal} />. Then you can trigger the closeModal function wherever you want inside the <ClientsTable> component.
Here is an example: https://codesandbox.io/s/material-demo-7nc1p
As you are already using open={modalClients}, and assuming your modalClients must be in a state. You can set this state to false to close the modal from your ClientsTable component like,
const ClientsTable = props => (
<div>
<button onClick={props.hideModal}>Hide</button>
</div>
);
And your modal should look like,
<Modal open={this.state.modalClients}>
<div className="">
<ClientsTable hideModal={() => this.setState({ modalClients: false })} />
</div>
</Modal>
Demo

Resources