Error: Invalid hook call - when calling a Modal component - reactjs

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/>}

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

React bootstrap modal not opening when onHide function is enabled

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.

Reusable Modal Component React Typescript

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.

Resources