I am trying to add react bootstrap modal in my application. In one file am dropping the Modal and trying to send the param to the modal class.
Example:
Class1 : I have this inside my render function.
<MyModal openModal={true}/>
I am trying to send the prop from here which will open the modal.
In my Modal class, I have below code:
const MyModal = (props) => {
const [open, showModal] = useState(props.openModal);
const handleClose = () => showModal(false);
return (
<Modal show={open} onHide={handleClose} >
//rest of the modal data
</Modal>
);
};
export default MyModal;
My prop is always showing the updated value coming from class 1. But Modal class is not getting rendered as per the update props. After closing the modal for 1st time, it remain close until I refresh my screen.
My using https://react-bootstrap.github.io/components/modal/ modal link for refrence.
Thanks in advance for help.
You are missing the hook to update the component when it receive the updated props.
You need to include useEffect() hook in your code. It will re-render the component with updated props. You MyModal class will look like below code.
const MyModal = (props) => {
const [open, showModal] = useState(props.openModal);
useEffect(() => {
showModal(props.openModal);
}, [props]);
const handleClose = () => showModal(false);
return (
<Modal show={open} onHide={handleClose} >
//rest of the modal data
</Modal>
);
};
export default MyModal;
Related
I'm prototyping a new version of our application in React 18. I'm somewhat new to React and have stumbled upon a scenario that has a few different problems.
We need to open a modal/dialog when a user performs an action. They will click a button to edit data, that opens a dialog window with a form. When they close the dialog, the form data is passed back to the component which opened it.
In our old app, it would be something like const user = new UserModal(123)
I'm using BlueprintJS's Dialog component for this, but this issue is applicable to any library.
I'm writing a wrapper because all of our modals will have similar functionality so the props to the Dialog component will never change outside of whether it's open or not.
Here's a super basic example of this "wrapper" component:
export const Modal = ({ isOpen }: ModalProps) => {
const [isOpen2, setIsOpen] = useState(isOpen);
const handleClose = useCallback(() => {
setIsOpen(false);
}, []);
return (
<Dialog isOpen={isOpen2}>
<p>this is a dialog</p>
<Button onClick={handleClose} text="close" />
</Dialog>
);
}
Using this in the parent would look like this:
const Demo = () => {
const [isOpen, setIsOpen] = useState(false);
// some code calls setIsOpen(true) when we need to open the modal
return <Modal isOpen={isOpen} />;
}
This presents multiple problems:
A parent controller can trigger this dialog to open, but never close (interacting with the app is prevented while a modal is open)
The modal can close itself via an X or "Cancel" button
This leads to two useState invocations - one in the parent controller and one inside the modal. This doesn't work right by itself, because once the state is set in the controller, it can't update when the prop changes with more code
The parent controller would need to know when it closes so it can update it's own state value.
I really dislike having to put <Modal> elements in the parent jsx, I liked the new UserModal code but that might be a fact of life
Overall, this feels like a very wrong approach. How can I design this to be more "proper" and yet work the way I need?
you can pass your method from parent to child and call there and also you can use 1 state for manage modal status.
export const Modal = ({ isOpen, handleClose, closeCallBack }: ModalProps) => {
const handleCloseChild = () =>{
closeCallBack()
handleClose()
}
return (
<Dialog isOpen={isOpen}>
<p>this is a dialog</p>
<Button onClick={handleCloseChild} text="close" />
</Dialog>
);
}
and parent something like this
const Demo = () => {
const [isOpen, setIsOpen] = useState(false);
// some code calls setIsOpen(true) when we need to open the modal
const handleClose = () =>{
setIsOpen(false)
}
return <Modal isOpen={isOpen} handleClose={handleClose} closeCallBack={() => // do what you want on close modal or you just do this in side modal or even in handelClose function } />;
}
im facing this weird behavior when trying to update the parent component with an set function to the child with props
this hook is to open and close the modal to edit an element
//PARENT FILE
//hook
const [isEditModalOpen, setEditModalOpen] = useState(false)
//more code...
//modal
{isEditModalOpen && <EditExcerciseModal setEditModalOpen={setEditModalOpen} isEditModalOpen={isEditModalOpen} />}
and this is the child code
//CHILD FILE
export const EditExcerciseModal = ({setEditModalOpen, excerciseInfo,fetchExcercisesFromRoutine})
//more code etc etc
<div className="addExcerciseModalContainer">
<span onClick={() =>{ setEditModalOpen(false) }} className="xModal">X</span>
i checked and the onClick is working. if i change the parent state manually the Modal works fine and closes.
the weird case when it its working is when instead of calling the set function i create a function with a setTimeout without time like this:
function closeModal(){
setTimeout(() => { setEditModalOpen(false)}, 0);
}
any ideas?
thanks for the help
You need to create a separation of concern. A Modal consists of three parts
The Modal of its Self.
The Content of the Modal.
And the container of the two.
You should be using the useState() hook and calling setEditModalOpen in the same containing component.
You need to make sure that you're declaring and setting state inside the same component.
// children would be the content of the modal
const Modal = ({ children, selector, open, setOpen }) => {
// we need the useEffect hook so that when we change open to false
// the modal component will re-render and the portal will not be created
useEffect(() => {
setOpen(false);
//provide useEffect hook with clean up.
return () => setOpen(true);
}, [selector]);
return open ? createPortal(children, selector) : null;
};
export const EditExerciseModal = ({ close }) => {
return (
<div>
{/* Instead of creating a handler inside this component we can create it in it's parent element */}
<span onClick={close}>X</span>
{/* Content */}
</div>
);
};
export const ModalBtn = () => {
const [isEditModalOpen, setEditModalOpen] = useState(false);
// this is where it all comes together,
// our button element will keep track of the isEditModalOpen variable,
// which in turn will update both child elements
// when true useEffect hook will re-render Modal Component only now it "will" createPortal()
// when our EditExerciseModal alls close it will set change the isEditModalOpen to false
// which will be passed to the Modal component which
// will then cause the component to re-render and not call createPortal()
return (
<>
<button onClick={() => setEditModalOpen(true)}>EditExerciseModal</button>
{setEditModalOpen && (
<Modal
open={isEditModalOpen}
setOpen={setEditModalOpen}
selector={'#portal'}>
<div className='overlay'>
<EditExerciseModal close={() => setEditModalOpen(false)} />
</div>
</Modal>
)}
</>
);
};
I have tried some of the methods, it could able to solve my solutions these are example :
this is the Parent Component :
const[openup,setOpenup]=useState(false)
const setSchedule =() => setOpenup(true)
<Button onClick={setSchedule} >Openup</Button>
<Child open={openup} />
this is the Chile Component :
export default function CreateSchedule(props) {
const [show, setShow] = useState(props.open);
const handleClose =() => setShow(false)
return(
<div>
<Modal className='dummy' show={show} onHide={handleClose}>
Its not working working !!!
</Modal>
</div>
)
}
Could any one get rid of this one , ...
Assuming Child is the CreateSchedule commponent, you can redesign your code structure so that the parent is controlling thee rendering of Modal by passing in props like open and also the function to close the modal
Your parent component will look like
const[openup,setOpenup]=useState(false)
const setSchedule =() => setOpenup(true)
const handleClose = () => setOpenup(false);
<Button onClick={setSchedule} >Openup</Button>
<Child open={openup} handleClose={handleClose}/>
Your Child component will be
export default function CreateSchedule(props) {
return(
<div>
<Modal className='dummy' show={props.open} onHide={props.handleClose}>
Its not working working !!!
</Modal>
</div>
)
}
Inside a Component, I have a Modal from which the user can do an update to some data through an API and then change the state of that main Component. Because there is a change in state, everything will re-render.
I would like to keep the modal open, so that I can show a success message.
The code would be something like this.
const Main = () => {
const [state, setState()] = useState();
return (
<Component state={state}>
<Modal onButtonClick={() => {
updateThroughApi().then(() => setState())} />
</Component>
)
}
When user clicks on modal's button, the state changes, and Component is re-rendered. Modal is rendered too, as it's inside.
I have thought of two possible solutions:
Move the modal outside of the component. This is a problem, as my actual code is not as simple as the example I posted. In my code, the modal opens on the click of a button B, which is deep inside Component. So, if I move the modal out from Component, I would have to pass the status and the action to change status (e.g. [open, setOpen]) through several components until button B (prop drilling).
Another solution: On the action onButtonClick I just do the API update, and use a new state updated = true; then, onModalClose, only if updated is true, I run setState so Component is rendered just after the modal is closed. But this solution seems a hack to me. There must be a better way.
Is there any better solution?
Something is obviously going very wrong here, the Modal should not close. As a workaround you could do something like this:
const Main = () => {
const [state, setState()] = useState();
const modal = useMemo(() => (
<Modal onButtonClick={() => {
updateThroughApi().then(() => setState())} />
), [])
return (
<Component state={state}>{modal}</Component>
)
}
Your Modal is re-rendering because your function passed as onButtonClick is redefined at every render.
So you have 2 options here:
1/ Keep your Modal inside your Component and use useMemo
import { useMemo } from 'react'
const Main = () => {
const [state, setState] = useState();
const modal = useMemo(() => (
<Modal onButtonClick={() => (
updateThroughApi().then(() => setState())
)}
/>
), [])
return (
<Component state={state}>
{modal}
</Component>
)
}
Or 2/ Move your Modal outside your component and use combination of memo and useCallback
import { memo, useCallback } from 'react'
const Main = () => {
const [state, setState] = useState();
const onButtonClick = useCallback(() => updateThroughApi().then(() => setState()), []);
return (
<Component state={state}>
<Modal onButtonClick={onButtonClick} />
</Component>
)
}
const Modal = memo(({onButtonClick}) => {
})
So in this case, at every render, memo will compare if all Modal props are === from previous render, which is now the case, because memoization of onButtonClick with useCallback, and so your Modal component will not re-render
https://reactjs.org/docs/hooks-reference.html#usememo
I have a modal that is completely self contained. The modal is opened via going to the modal route and all the functionality to close the modal from button or outside clicks is within the modal component. Basically the modal is not controlled by any parent passing state. I was given a task of making the modals button customizable, meaning passing in a new button component, so we can add the modal to our lib instead of copy pasting the code in projects. Lol this seemed simple enough, and maybe it is and I am just overthinking this.
I cant paste the actual code but I can use a contrived example. This is a very simplified version of the modal, keeping in mind it opens via route so there's really no state and setState in the actual code. Also here is a fiddle
const ModalHeader = ({ onClose }) => {
return (
<div className="modal__header">
<button
className="modal__close-btn"
data-testid="modal-close-button"
onClick={onClose}
/>
</div>
);
};
const Modal = ({ children }) => {
const [state, setState] = React.useState(true);
const handleCloseOutsideClick = () => {
setState(false);
};
const handleCloseButtonClick = () => {
setState(false);
};
const renderModal = () => {
return (
<div className="modal-overlay" onClick={handleCloseOutsideClick}>
<div className="modal">
<ModalHeader onClose={handleCloseButtonClick} />
{children}
</div>
</div>
);
};
return state ? renderModal() : null;
};
const App = () => {
return (
<Modal>
<div>Modal Children</div>
</Modal>
);
};
ReactDOM.render(<App />, document.querySelector('#app'));
I tried a few things, initially I attempted to find a way to pass in a new header component containing a button. Then as I got into the code I realized what I was doing would lose the self contained functionality of the modal. My approach was along the lines of below but obviously the onClick would be an issue since invoking the close functionality is internal.
So I tried using cloneElement to add props within the component if the custom header was detected:
// inside modal component
React.useEffect(() => {
React.Children.map(children, (child: React.ReactElement) => {
if (child && child.type === ModalHeader) {
setHederFound(true);
}
});
}, []);
// inside modal render:
<div className={modalClasses} onClick={stopPropagation}>
{!headerFound ? (
<ModalDefaultHeader onClose={handleCloseButtonClick} />
) : (
React.Children.map(children, (child: React.ReactElement) => {
if (child && child.type === ModalHeader) {
return React.cloneElement(child, {
onClose: handleCloseButtonClick,
});
}
})
)}
{children}
</div>;
Obviously that did not work because there's no onClick in the custom button. Anyways I am thinking that I am over complicating this. I just need a way to pass in a custom button while leaving the functionality internal to the modal. Any assistance would be appreciated.
Thanks in advance.