I have an antd modal as shown in the below code, Now when I select Create Manual and click Next, I want to close this modal and open another Modal2 but another modal is not getting opened after clicking next.
Here is my code. ( Codesandbox live demo - link )
Please suggest a workaround to get his second modal generated. Thanks
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Modal, Button, Radio } from "antd";
const App = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [selectRadio, setselectRadio] = useState("preselect");
const showModal = () => {
setIsModalVisible(true);
};
const select = (e) => {
// you can save the value in here
setselectRadio(e.target.value);
console.log(e.target.value);
};
function modalclick() {
if (selectRadio === "preselect") {
alert("pre-select");
} else {
//---------------> UNABLE TO OPEN ANOTHER MODAL HERE <-------------------------------------
<Modal title="Create Test Suite" visible={isModalVisible}>
MODAL 2 COMES HERE
</Modal>;
alert("manual");
}
}
return (
<>
<Button type="primary" style={{ float: "right" }} onClick={showModal}>
Create Test Suite
</Button>
<Modal
title="Create Test Suite"
visible={isModalVisible}
footer={[
<Button key="cancel" onClick={() => setIsModalVisible(false)}>
Cancel
</Button>,
<Button type="primary" key="next" onClick={modalclick}>
Next
</Button>
]}
>
<Radio.Group
defaultValue="preselect"
buttonStyle="solid"
onChange={(e) => {
select(e);
}}
>
<Radio value="preselect">Create from Preselect</Radio>
<Radio value="manual">Create Manual</Radio>
</Radio.Group>
</Modal>
</>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
To show the modal 2 you can use a useState hook or a useRef hook. In both methods, you need first to put this modal 2 in the return of your "App".
useState way: Just use a state to control the visibility, like how you do in modal 1, simple.
useRef way: This is a little more complex. You will need to use a useImperativeHandle inside the modal component, and create a function (inside too) to control the visibiliity. So, in your page, you can just call the function that is inside the component, to show the modal. Using this method, the logic about the state control of visibility leaves the page and goes into the component.
Modal2 is not visible because is out of the return of App function. Put Modal 2 inside return and try again.
Related
I originally had the code for my modal in the same component as the parent page and it worked fine, but now I've moved it out into it's own component I can no longer close the modal once it has been opened. I'm aware there are similar issues posted previously but they all seem to be using class components and I can't get the solutions to work for me.
I've stripped out irrelevant code for readability's sake
Main component
import { LoginModal } from '../partials/LoginModal';
export function SignUpButtonGroup() {
const [showLogin, setShowLogin] = useState(false);
const handleCloseLogin = () => setShowLogin(false);
const handleShowLogin = () => setShowLogin(true);
return (
<Container>
<LoginModal showLogin={showLogin} onHideLogin={handleCloseLogin}></LoginModal>
...
<Button onClick={handleShowLogin} className="btn-sign-in btn btn-lg mx-auto" variant="primary">sign in
</Button>
Modal component
import { Modal } from 'react-bootstrap';
export function LoginModal(props) {
return (
<Fragment>
<Modal show={props.showLogin} onHide={props.handleCloseLogin}>
</Modal>
You need to refer to the prop by its correct name props.onHideLogin in the Modal component.
It's only called handleCloseLogin in the Main component.
I encountered an issue trying to close the headlessui Disclosure modal inside the panel.
My goal is to have a button inside the panel which can close the modal.
The way I tried to solve this problem is doing it manually using useRef, but it works partially.
After opening the panel for the first time, you can close the modal but if you try to open it again, it doesn't work. Can't figure out how to solve this issue.
Any help will be appreciated.
Here is the codesandbox link
And here is the code
import { Disclosure } from "#headlessui/react";
import React, { useState, useRef } from "react";
import CloseIcon from "#material-ui/icons/Close";
import ExpandMoreIcon from "#material-ui/icons/ExpandMore";
const App = () => {
const [isClosed, setIsClosed] = useState(false);
const modalRef = useRef(null);
const hideModalHandler = (e) => {
e.preventDefault();
modalRef.current?.click();
setIsClosed(!isClosed);
};
return (
<Disclosure>
{({ open }) => (
<div ref={modalRef}>
<Disclosure.Button>
<span>modal</span>
<ExpandMoreIcon />
</Disclosure.Button>
{!isClosed && (
<Disclosure.Panel>
<CloseIcon onClick={hideModalHandler} />
<div>name</div>
</Disclosure.Panel>
)}
</div>
)}
</Disclosure>
);
};
export default App;
I haven't used headlessui Disclosure but I see that the function hideModalHandler isn't actually hiding but toggling. Did you mean setIsClosed(true) instead of setIsClosed(!isClosed)?
Also, after a quick look at the documentation, have you tried using the close from the headlessui Disclosure? You don't need useRef
Use the state, and wrap the disclosure button into a DIV
with onClick and some ID string to identify what disclosure must be open. Something like this (works for multiple disclosures):
const [keyOfOpenDisclosure, setKeyOfOpenDisclosure] = useState('')
const toggleDisclosure = (key: string) => {
setKeyOfOpenDisclosure((prev) => (prev !== key ? key : ''))
}
...
<Disclosure>
<div onClick={() => toggleDisclosure(someId)}>
<Disclosure.Button>
Text of disclosure button
</Disclosure.Button>
</div>
<Transition
show={someId === keyOfOpenDisclosure}
...
What I want to do is to create a reusable and convenient way of showing an alert or a confirmation modal.
Using library modals usually require you to import a Modal component and create a state variable and pass it as a prop to the imported component to control its visibility.
What I want to do is to create a custom hook that exports a modal component with all the customization (maybe a wrapper around a Modal component from a library) and a function to toggle the visibility.
Something like below.
This is the hook code:
import React, {useState} from 'react'
import 'antd/dist/antd.css'
import {Modal as AntdModal} from 'antd'
const useModal = () => {
const [on, setOn] = useState(false)
const toggleModal = () => setOn(!on)
const Modal = ({onOK, ...rest}) => (
<AntdModal
{...rest}
visible={on}
onOk={() => {
onOK && onOK()
toggleModal()
}}
onCancel={toggleModal}
/>
)
return {
on,
toggleModal,
Modal,
}
}
export default useModal
And this is how I use it:
import React, {useState} from 'react'
import ReactDOM from 'react-dom'
import useModal from './useModal'
import {Button} from 'antd'
const App = () => {
const {toggleModal, Modal} = useModal()
return (
<div>
<Button type="primary" onClick={toggleModal}>
Open Modal
</Button>
<Modal title="Simple" onOK={() => alert('Something is not OK :(')}>
<p>Modal content...</p>
</Modal>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
Here is a sandbox to see it in action and test it out. There are two buttons, one which shows a Modal which is normally imported from the library (here antd) and one that is from a custom hook useModal.
The one form the hook works except it seems something is wrong with it. The appearing transition is working but when you close the modal it suddenly disappears with no transition. It seems the component is immediately destroyed before transitioning out. What am I doing wrong?
If I understand it correct, you want to render a Component and also need a function which can control it (toggle it's visibility).
Though it is not possible the way you are trying to achieve with the react hooks, because on state change you are actually updating your Modal too and that is causing an unmount of the Dialogue from DOM.
You can use below solution to achieve the same result. The Solution uses a component with forwardRef and useImperativeHandle and will achieve a decoupled function which you can use to toggle your dialogue using button click:
NOTE: You need to upgrade to react and react-dom from v-16.7.0-alpha (as in your sandbox code) to latest (16.14.0) [I have not tried other intermediate versions]
Modal Component:
import React, {useState, forwardRef, useImperativeHandle} from 'react'
import 'antd/dist/antd.css'
import {Modal as AntdModal} from 'antd'
const Modal = forwardRef(({onOK, ...rest}, ref) => {
useImperativeHandle(ref, () => ({
toggleModal: toggleModal
}));
const [on, setOn] = useState(false)
const toggleModal = () => setOn(!on)
return (
<AntdModal
{...rest}
visible={on}
onOk={() => {
onOK && onOK()
toggleModal()
}}
onCancel={toggleModal}
/>
)
});
export default Modal;
And this is how to use it:
import React, {useState, useRef} from 'react'
import ReactDOM from 'react-dom'
import Modal from './ModalWrapper'
import {Button, Modal as AntdModal} from 'antd'
const App = () => {
const [on, setOn] = useState(false)
const toggle = () => setOn(!on)
const modalRef = useRef()
return (
<div>
<Button type="warning" onClick={() => setOn(true)}>
Normal Import
</Button>
<br />
<br />
<Button type="primary" onClick={() => modalRef.current.toggleModal()}>
From Modal Component
</Button>
<AntdModal visible={on} onOk={toggle} onCancel={toggle}>
<p>I was imported directly...</p>
<p>I was imported directly...</p>
<p>I was imported directly...</p>
</AntdModal>
<Modal
title="Simple"
ref={modalRef}
onOK={() => alert('Things are now OK :)')}
>
<p>I was imported from Modal Component...</p>
<p>I was imported from Modal Component...</p>
<p>I was imported from Modal Component...</p>
</Modal>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
I hope it will help your use case.
Thanks.
I have the following component which makes up my modal:
import React from 'react';
import { ModalBody, Button, Alert } from 'bootstrap';
import { AppModalHeader } from '../../common/AppModalHeader';
import ModalWrapper from './ModalWrapper';
const QuestionModal= ({
title,
noText = 'No',
yesText = 'Yes',
questionText,
onYesAction
children
}) => {
const { toggle, isOpen, openModal } = useModalForm();
return (
<React.Fragment>
<ModalWrapper className={className} isOpen={isOpen} toggle={toggle}>
<AppModalHeader toggle={toggle}>{modalTitle}</AppModalHeader>
{isOpen ? (
<ModalBody>
<p>{questionText}</p>
<Button
className="float-right"
color="primary"
onClick={() => {
if (onYesAction !== undefined) {
onYesAction(toggle);
}
}}
>
{yesText != null ? yesText : 'Yes'}
</Button>
</ModalBody>
) : null}
</ModalWrapper>
{children({
triggerModal: () => openModal({ id: undefined }),
toggle
})}
</React.Fragment>
);
};
export default QuestionModal;
I want to use it as such, where I can dynamically choose the name of the trigger that opens the modal:
In use e.g. (note: the inner question modal would be repeated, used 4 or 5 times in my application):
....
<QuestionModal
//....params that match up with above
>
{({ triggerModal }) => (
<QuestionModal
//....params that match up with the component
>
{({ triggerModal2 }) => (
<>
<Button onClick={()=>triggerModal();}>Trigger Modal 1</Button>
<div>
<Button onClick={()=>triggerModal2();}>Trigger Modal 2</Button>
</div>
</>
</>
)}
</QuestionModal>
....
How could I achieve this, by extending the question modal to pass a dynamic function? Just because I keep getting stuck in having to think about duplicating the original component, I want to make this component as reusable as I can. Any help would be greatly appreciated.
Thanks in advance
I think you're overcomplicating things. The problem is you're trying to control whether or not the modal is rendered from inside the modal itself. If you really want to have reusable components, it's good to decouple presentation from logic. In your case, you want to have a modal component with all the presentation/layout/styling stuff and pass in via props the actual content.
For example:
import React from 'react';
import { ModalBody, Button, Alert } from 'bootstrap';
import { AppModalHeader } from '../../common/AppModalHeader';
import ModalWrapper from './ModalWrapper';
const QuestionModal= ({
title,
noText = 'No',
yesText = 'Yes',
questionText,
onYesAction
children
}) => {
return (
<React.Fragment>
<ModalWrapper>
<AppModalHeader toggle={toggle}>{title}</AppModalHeader>
<ModalBody>
<p>{questionText}</p>
<Button
className="float-right"
color="primary"
onClick={onYesAction}
>
{yesText}
</Button>
</ModalBody>
</ModalWrapper>
</React.Fragment>
);
};
export default QuestionModal;
Now this is a purely presentational component, it creates a skeleton in which you put the actual content. And for using it, you'll control whether or not the modal is rendered from where it is actually used, like so:
import React, {useState} from 'react';
import QuestionModal from './QuestionModal'
const SomeComponent = (props) => {
const [showModal, setShowModal] = useState(false);
const toggleModal = () => {
setShowModal(!showModal);
}
const yesActionLogic = () => {
// Your yes-action logic...
}
return (
<div>
{showModal ? (
<QuestionModal
title="Sample title",
questionText="Question?"
onYesAction={yesActionLogic}
/>
) : null}
<Button onClick={toggleModal}>Toggle Modal</Button>
{/* The rest of your stuff... */}
</div>
);
}
If you want to create reusable components, it's good practice to not put any business logic on it. Use props to pass in functions that will be triggered from inside the components, and lift all the work to the components that actually hold your business logic.
One of the SOLID principles of software engineering is called Single-responsibility principle, and you can apply it to your React components:
Your Modal component is responsible for displaying data in its correct layout and triggering some set of functions from outside, regardless of what data/logic you pass.
This Modal component will be used by some other component whose responsibility is to show the user a modal with some specific data, at the right time.
So it makes sense that you should toggle your modal from outside.
On a personal note, I like to structure a React app in components that hold only presentational logic, and are used by containers, which are more logic-dense (generally having async requests).
In our react app (we use reactstrap), we've multiple pages from where a confirmation modal can be shown. We do not want to include the modal code in every page. Is there a way to do this programmatically by invoking a method?
We can use plain bootstrap modals directly in the public index.html and from the util method use dom selector and invoke the modal but want to avoid this. Any pointers on how to go about this?
If what you want is only one modal which can be used across multiple pages(instead of putting one modal in every page), you can put it in the root component usually names as App.
import Modal from "somewhere";
function App() {
const [modal, setModal] = useState(false);
return <>
<Model isOpen={modal} />
{/* If you need to toggle modal when clicking something in PageA, you can pass the prop down like this */}
<PageA onToggleModel={()=>{setModal(!modal)}} />
<PageB />
</>;
}
Just in case, doing import Modal from "somewhere" in every page wouldn't result in duplicate code in your final bundle. It's totally fine to do that.
Here's what we did.
Alert Component:
import React, { useState } from "react";
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
const Alert = props => {
const [modal, setModal] = useState(props.open ? props.open : true);
const toggle = () => {
setModal(!modal);
if (props.cb) {
props.cb();
}
if (props.reloadPage) {
window.location.reload();
}
};
return (
<div>
<Modal isOpen={modal} toggle={toggle}>
<ModalHeader toggle={toggle}>{props.title}</ModalHeader>
<ModalBody>{props.text}</ModalBody>
<ModalFooter>
<Button color="primary" onClick={toggle}>
Ok
</Button>
</ModalFooter>
</Modal>
</div>
);
};
export default Alert;
Util.js:
import React from "react";
import ReactDOM from "react-dom";
import Alert from "./Alert";
const Util = {
alert: (message, okCb, reload) => {
ReactDOM.render(
<Alert
title="Done"
text={message}
cb={() => {
ReactDOM.unmountComponentAtNode(
document.getElementById("modalHolder")
);
if (okCb) {
okCb();
}
}}
reloadPage={reload}
/>,
document.getElementById("modalHolder")
);
}
};
export default Util;
In index.html we created a dom element:
<div id="modalHolder"></div>
So to invoke the modal imperatively, call:
Util.alert("Data has been saved")