React bootstrap modal not opening when onHide function is enabled - reactjs

I am noticing that my React Bootstrap modal wont open when I put the following condition : onHide={props.setShowModal(false)}. When I change it to onHide={props.setShowModal(true)} then it always stays open.
What is going wrong ?
PARENT JS --------
const [showModal, setShowModal] = useState(false);
<AddModal showModal={showModal} setShowModal={setShowModal} />
MODAL -------
import React, { useState, useEffect } from 'react';
import { Button, Form, FormControl, Modal } from "react-bootstrap";
const AddModal = (props) => {
const handleClose = () => {
props.setShowModal(false);
}
return (
<Modal show={props.showModal} animation={false}
// onHide={props.setShowModal(false)}
>
<Modal.Header closeButton>
<Modal.Title>Enter Item</Modal.Title>
</Modal.Header>
{
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}
>
Cancel
</Button>
<Button variant="primary" onClick={handleClose}
>
Save Changes
</Button>
</Modal.Footer>}
</Modal>
);
}
export default AddModal;

This happens because props.setShowModal(false) is immideatly called when your modal is rendered. You can fix it by changing your code to onHide={() => props.setShowModal(false)}.
This is a common trap. onHide (as well as onClick and any other event handlers) expects a function that will be called when event is fired. props.setShowModal is a function, props.setShowModal(false) is not. So your options are either pass a lamda function as I suggested, or create a function
hideModal() {
props.setShowModal(false)
}
and pass it to onHide.

Related

How to call an individual modal box component in react?

I wanted to open/load a react bootstrap model box component (Modalbox.js) to App.js, but the modal box open button code in App.js, how to do that? please help.
In App.js, there will be multiple buttons for call multiple type modalboxs. modalbox.js should contain only modal box code. modal launch buttons should be in App.js
<button variant="primary" onClick={handleShow}>Launch demo modal</button>
https://codesandbox.io/s/individual-modal-box-component-ti6ugg
I have added only functionality on the show and hide here is code so that you may get the idea props and manage the state accordingly all remaining code it feels free to add more functionality and custome state
import "./styles.css";
import Modalbox from "./Modalbox";
import { useState } from "react";
export default function App() {
const[handleShow,sethandleShow] = useState(false)
return (
<div className="App">
<button variant="primary" onClick={()=>sethandleShow(!handleShow)}>
Launch demo modal
</button>
<Modalbox handleShow={handleShow} sethandleShow={sethandleShow} />
</div>
);
}
modalbox.js
import React, { useState } from "react";
import { Modal, Button } from "react-bootstrap";
function Modalbox(props) {
const [modalshow, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<div>
{/* <Modal show={modalshow} onHide={handleClose}> */}
<Modal show={props.handleShow} onHide={()=>props.sethandleShow(!props.handleShow)}>
<Modal.Header closeButton>
<Modal.Title>Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>Woohoo, you're reading this text in a modal!</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={()=>props.sethandleShow(!props.handleShow)}>
Close
</Button>
<Button variant="primary" onClick={handleClose}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
</div>
);
}
export default Modalbox;
You should create a state in App.js called "showModal" which is a boolean. Pass that state as props to Modal. also in your handleshow function you should setState based on the prevState like so: setShowModal((prevState)=> !prevState). In your modal component you should use the showModal prop you passed but remember never use props as the defaut value in useStates.
You put the state for showing the Modalbox in it, but it doesn't know from outside when to show. I'm afraid you have to at least put the show state in App.js. The rest of the logic can remain in Modalbox.js.
I forked your condesandbox:
https://codesandbox.io/s/individual-modal-box-component-forked-moy9ok
The other possibility is to use the Context API and maybe write a hook for it. Then you wouldn't need to directly pass the state down to Modalbox. But it's not really a suitable use case for the Context API. It's rather for prop drilling.

React-Bootstrap input text lose focus when opened from a OverlayTrigger in a Modal

I have a button in a modal that on click opens a popover component, in the popover I have an input text field.
The problem I have is that the input lose focus instantly, I can't type in it because something else is hijacking the focus out of it and I can't figure out what is, here is a working example of the problem.
And here is the code in question:
import "./styles.css";
import { Fragment, useState } from "react";
import { Button, Modal, OverlayTrigger, Popover, Form } from "react-bootstrap";
export default function App() {
const [showModal, setShowModal] = useState(false);
const [inputValue, setInputValue] = useState("");
const popover = (
<Popover id="popover-basic">
<Popover.Content>
<Form.Control
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Enter value"
/>
</Popover.Content>
</Popover>
);
return (
<div className="App">
<Fragment>
<Button variant="primary" onClick={() => setShowModal(true)}>
Open
</Button>
{showModal && (
<Modal
show={showModal}
onHide={() => setShowModal(false)}
centered
backdrop="static"
animation={false}
>
<Modal.Header closeButton>modal</Modal.Header>
<Modal.Body>
<p>Hello</p>
<OverlayTrigger
trigger="click"
placement="right"
overlay={popover}
>
<Button variant="secondary">Open Popover</Button>
</OverlayTrigger>
</Modal.Body>
</Modal>
)}
</Fragment>
</div>
);
}
Modal has a property "enforceFocus", which keeps focus on the Modal component. The property value is set to true per default. Set it to false and you will be able to use your input.
"https://react-bootstrap-v4.netlify.app/components/modal/#modal-props
Disabling the enforceFocus works, but I'm not sure it is the best alternative. If the user navigates the fields using the tab key, he might focus elements outside of the modal, which could create confusion.
Instead, I suggest you play with the overlay's container prop. You could use a ref to the modal itself, or to a container inside the modal. This way, the overlay will be "inside" the modal and won't lose focus.
I created an example to demonstrate it works.

React set multiple states on an event

I have a modal that when it is closed updates a state. The modal also has a div which will be replaced if a button is clicked. The button is replaced with a text area and 2 buttons (one of them a cancel.) If the cancel is clicked, the state updates and the text area hides. All good. However, if the user closes the modal, then the state is not updated and the div is shown next time.
I am unsure of how to set 2 states on close for the modal, I think this could sort this issue.
Code has been updated as per #jsNoob suggestion:
Hint component has
const [showModalProblemS_vid, setShowModalProblemS_vid] = useState(false);
<VideoModal showModal={showModalProblemS_vid} closeHandler={() => setShowModalProblemS_vid(false)} videoMessage={props.prob_s_vid} size='med'/>
So how to set a state which is not in the file is the issue
Modal Code below:
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';
import { useState } from 'react';
function VideoModal({showModal = false, closeHandler = () =>{}, videoMessage, content, size}) {
const [confused, setConfused] = useState(false)
return (
<Modal
size={size}
show={showModal}
onHide={closeHandler}
onClose={()=> {setConfused(false); closeHandler()}}
backdrop="static"
keyboard={false}
>
<Modal.Body>
<video src={videoMessage} controls autoPlay></video>
<div>
{confused ? (
<div>
What have you found confusing about this video?
<textarea className='confusedText' rows="2"></textarea>
<Button className="confusedBtnSave">
Save
</Button>
<Button className="confusedBtnCancel" onClick={()=>setConfused(false)}>
Cancel
</Button>
</div>
) : (
<div>
<Button className="confusedBtn" onClick={()=>setConfused(true)}>
Confused?
</Button>
</div>
)}
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={closeHandler}>
Close
</Button>
</Modal.Footer>
</Modal>
)
}
export default VideoModal
Well to set multiple states you can do that inline
<Button onClick={() => {setState1(foo); setState2(bar)}}></Button
Or you could add them to a function and then call the function inline
function multipleStates(foo, bar){
setState1(foo)
setState2(bar)
}
<Button onClick={() => multipleState(foo, bar)}></Button>
I'm not sure if that is too broad of an answer for you, hopefully I haven't misinterpreted your question.
Got this working. I had to use the following:
onClick={() => {onClose(); setConfused(false)}}
note the semi-colon between the states

Error: Invalid hook call - when calling a Modal component

When I try to call the Modal component I'm getting this error -
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem.
How can I show this modal when pressing a button in functional component?
Thanks
Search.jsx
import AddedToCart from '../modals/Modal';
const Search=()=>{
return(
<div>
<button
onClick={() => AddedToCart()}
>
</div>
)
}
Modal.jsx
import Modal from 'react-bootstrap/Modal';
import React, { useState } from 'react';
export default function AddedToCart() {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<button variant='primary' onClick={handleShow}>
Launch demo modal
</button>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>Woohoo, you're reading this text in a modal!</Modal.Body>
<Modal.Footer>
<button variant='secondary' onClick={handleClose}>
Close
</button>
<button variant='primary' onClick={handleClose}>
Save Changes
</button>
</Modal.Footer>
</Modal>
</>
);
}
Any help will be awesome.
onClick={() => AddedToCart()}
You are calling it like a function in a click handler, that is not how you render stuff in react.
instead set a flag
onClick={() => setOpenModal(true)}
and then in render
{openModal && <AddedToCart/>}

Closing Material UI Dialog from child component triggers parent's Dialog to open

I have 2 Dialogs, one on outer scope (parent) and one as a child.
export default function App() {
const [parentDialogOpen, setParentDialogOpen] = useState(false);
const [childDialogOpen, setChildDialogOpen] = useState(false);
const handleParentClick = () => {
setParentDialogOpen(true);
};
const handleChildClick = (e) => {
e.stopPropagation();
setChildDialogOpen(true);
};
return (
<div className="App">
<Dialog
open={parentDialogOpen}
onClose={() => setParentDialogOpen(false)}
>
<Box p={4}>Parent Dialog</Box>
</Dialog>
<Box bgcolor="red" p={2} onClick={handleParentClick}>
<Dialog
open={childDialogOpen}
onClose={(e) => setChildDialogOpen(false)}
>
<Box p={2}>Child Dialog</Box>
</Dialog>
<button onClick={handleChildClick}>Child</button>
</Box>
</div>
);
}
When I click on the button, it opens a dialog (Child Dialog), but when I close it, the Parent Dialog pops up. My expected behavior is that clicking on the child button should not fire onClick event on the parent (as I've added e.stopPropagation() to Child's onClick handler)
Adding e.stopPropagation() on handleChildClick does stop the event to propagate to Parent's onClick, but when I close the Dialog, Parent's onClick still fires.
I have also tried adding e.stopPropagation() to the Child Dialog's onClose, but it does not help.
Although moving the Child Dialog to the same level as Parent Dialog makes it work as expected, I cannot do that because in my (real) code I have to dynamically render the child component which the child component may have it's own Dialog.
Is there a way to solve this problem?
[UPDATE]: Seems like Parent Dialog's onClick event also fire when clicking ANYWHERE when Child Dialog is open. For example, clicking INSIDE child dialog will fire Parent Dialog's onClick (without closing Child Dialog)
Here is a Code Sandbox: https://codesandbox.io/embed/eloquent-hypatia-nuksu?fontsize=14&hidenavigation=1&theme=dark
Just stop the propagation in the onClick event of the Dialog (see below)
import "./styles.css";
import { Box, Dialog } from "#material-ui/core";
import { useState } from "react";
export default function App() {
const [parentDialogOpen, setParentDialogOpen] = useState(false);
const [childDialogOpen, setChildDialogOpen] = useState(false);
const handleParentClick = () => {
setParentDialogOpen(true);
};
const handleChildClick = (e) => {
e.stopPropagation();
setChildDialogOpen(true);
};
return (
<div className="App">
<Dialog
open={parentDialogOpen}
onClose={() => setParentDialogOpen(false)}
>
<Box p={4}>Parent Dialog</Box>
</Dialog>
<Box bgcolor="red" p={2} onClick={handleParentClick}>
<Dialog
open={childDialogOpen}
onClick={(e) => e.stopPropagation()}
onClose={(e) => setChildDialogOpen(false)}
>
<Box p={2}>Child Dialog</Box>
</Dialog>
<button onClick={handleChildClick}>Child</button>
</Box>
</div>
);
}
if your aim is to trigger parent dialog open when child is closed you could go with the following;
...
...
<Dialog
open={childDialogOpen}
onClose={(e) => {
setChildDialogOpen(false);
setParentDialogOpen(true);
}}
>
Then get rid of handleParentClick for good

Resources