monkeys73
monkeys73

Reputation: 171

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.

Upvotes: 1

Views: 11892

Answers (2)

trixn
trixn

Reputation: 16309

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>

Edit quizzical-sunset-wzjfu

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>
  );
}

Edit keen-cray-7pf5k

Upvotes: 3

sulaimanh
sulaimanh

Reputation: 21

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.

Upvotes: 1

Related Questions