3

I'm trying to create a pop up subscription box which closes whenever one clicks on the close button or clicks anywhere outside the pop up. I've created a modal component and a state showModal which is used to toggle the visibility of this Modal. I have tried to add setShowModal(false) to the outer div element but that just disables the whole modal. What can be done to close the modal whenever we click outside the modal. This is how my main page looks

const [showModal, setShowModal] = useState(false);

return (
    <>
      <div
        className="homepage"
        style={{
          filter: showModal ? "blur(8px)" : "none",
          minHeight:"80vh",
        }}
      >
        <section
          className="homepage-hero"
          style={{ paddingBottom:"-2rem", minHeight:"100vh" }}
        >
          <div className="hero-body">
            <div className="container">
              <div className="columns">
                <div className="column ">
                  <h1>
                    <span className="heading">
                      Finance <br />
                      Scheme
                    </span>
                    <br />
                  </h1>
                  <p>
                   Lorem Ipsum
                  </p>
                  <div className="is-hidden-tablet">

                  </div>
                  <div className="button-group">
                    <button
                      style={{
                        fontWeight: "600",
                        padding: "0.75em 1.9em",
                        borderRadius: "0px",
                        color: "white",
                        backgroundColor: "#24ca7a",
                        border: "1px solid #24ca7a",
                        cursor: "pointer",
                      }}
                      onClick={() => setShowModal(true)}
                    >
                      Download
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </section>
      </div>
      {showModal && (
        <Modal
          modalId="signup-modal"
          onClose={() => setShowModal(false)}
          canDismiss={false}
          modalWidth="70%"
          style={{position:"fixed", top:"20", minHeight:"50vh"}}
          >        
          <div className="contact-us" style={{  margin:"20px"}}>
            <section className="contact-form"  >
              <div className="container">
                <div className="columns is-8 ">
                  <div
                    className="column is-half-desktop is-full-mobile container-content"
                    style={{ boxShadow: "none" }}
                  >
                    {submitted ? (
                      <div className="success">
                        <img src={confirmedIllus} alt="" />
                        <h2>Your details have been submitted successfully!</h2>
                      </div>
                    ) : (
                      <form onSubmit={handleOnSubmit} id="contact-form" >
                        <h1 className="heading" style={{ fontSize: "2rem" }}>
                          Your Details
                        </h1>
                        <br />
                        <div className="field">
                          <label className="label">Name</label>
                          <div className="control">
                            <input
                              className="input"
                              id="name"
                              value={name}
                              type="text"
                              placeholder="Your Full Name"
                              onChange={(e) => setName(e.target.value)}
                              required
                            />
                          </div>
                        </div>
                        <div className="field">
                          <label className="label">Email</label>
                          <div className="control ">
                            <input
                              className={`input ${isDanger}`}
                              id="email"
                              value={email}
                              type="email"
                              onChange={handleOnChange}
                              placeholder="Your Email"
                              required
                            />
                            {!validEmail && isDanger ? (
                              <span className="icon is-small is-right">
                                <i className="material-icons-round">warning</i>
                              </span>
                            ) : (
                              " "
                            )}
                          </div>
                          {!validEmail && isDanger ? (
                            <p className="help is-danger">{emailMessage}</p>
                          ) : (
                            ""
                          )}
                        </div>

                        <div className="field is-grouped submit-button-group">
                          <div className="control">
                            <button
                              style={{
                                cursor: !validEmail ? "not-allowed" : "pointer",
                              }}
                              className="button  submit-button"
                              id="submit-form"
                            >
                              Submit
                            </button>
                          </div>
                        </div>
                      </form>
                    )}
                  </div>
                  <div className="column is-half-desktop is-full-mobile " >
                    <img
                      src="/images/Ebook.svg"
                      className="is-hidden-mobile"
                      style={{ width: "70%", marginTop: "40%" }}
                    />
                    <div className=" font-blue bottom-text">
                      Fill your details to download the free <b> Ebook </b>
                    </div>
                  </div>
                </div>
              </div>
            </section>
          </div>
        </Modal>
      )}
    </>
  );
Cassandra
  • 147
  • 1
  • 2
  • 15

3 Answers3

15

You can use the useOnClickOutside hook. This hook allows you to detect clicks outside of a specified element.

You have to import the followings

Create a ref that we add to the element for which we want to detect outside clicks

const ref = useRef();

State for our modal

const [showModal, setShowModal] = useState(false);

Call hook passing in the ref and a function to call on outside click

useOnClickOutside(ref, () => setShowModal(false));

render here

return(...);

//Hook

import { useEffect } from 'react';


export default function useOnClickOutside(ref, handler) {
  useEffect(
    () => {
      const listener = (event) => {
        // Do nothing if clicking ref's element or descendent elements
        if (!ref.current || ref.current.contains(event.target)) {
          return;
        }
        handler(event);
      };
      document.addEventListener("mousedown", listener);
      document.addEventListener("touchstart", listener);
      return () => {
        document.removeEventListener("mousedown", listener);
        document.removeEventListener("touchstart", listener);
      };
    },
    // Add ref and handler to effect dependencies
    // It's worth noting that because the passed-in handler is a new ...
    // ... function on every render that will cause this effect ...
    // ... callback/cleanup to run every render. It's not a big deal ...
    // ... but to optimize you can wrap handler in useCallback before ...
    // ... passing it into this hook.
    [ref, handler]
  );
}

See related repl output here - https://spanishhotloaderprogram.thelovekesh.repl.co/

Alireza Bagheri
  • 207
  • 3
  • 15
thelovekesh
  • 1,364
  • 1
  • 8
  • 22
  • Will I have to import this hook or is it a custom hook? Sorry I'm new to React – Cassandra Jun 17 '21 at 11:33
  • No, you have to just copy and paste this hook. But you have to include this - `import { useState, useEffect, useRef } from "react";` – thelovekesh Jun 17 '21 at 11:35
  • 1
    Where should this hook `useOnClickOutside(ref, () => setShowModal(false));` be called – Cassandra Jun 17 '21 at 11:42
  • above the return.. I write it in the sequence in the answer. – thelovekesh Jun 17 '21 at 11:43
  • 2
    This also closes the modal when we click inside the modal, the modal should only close when we click outside it, shouldn't we limit this to everything except modal, you can check out my modal component to see the onClose event [link](http://codesandbox.io/s/laughing-feynman-sqn9d) – Cassandra Jun 17 '21 at 11:47
  • bro, you have implemented something wrong.. check the demo link its will not close when you click inside it. – thelovekesh Jun 17 '21 at 11:49
  • How did you add ref to the corresponding element? – Cassandra Jun 17 '21 at 11:59
  • 1
    You can see the code here - https://replit.com/@thelovekesh/SpanishHotLoaderprogram Just go to src/app.jsx – thelovekesh Jun 17 '21 at 12:02
  • It works great! Thanks also for the explanation! I got it working after looking at the code you linked in the comments. Maybe you could update the answer making more clear that you have to put `ref={ref}` in the element representing the modal. – Pietro Aug 12 '22 at 07:30
  • I see the same failure as @Cassandra -- the useOnClickOutside hook is firing the handler on a click inside the React element as well outside. Please share code using a more widely-known source such as CodePen. – Tom Stambaugh Oct 13 '22 at 22:00
  • I needed to add a `ref={ref}` attribute to my element or current is never set but then when I click inside the element, I'm seeing the handler trigger twice; the first time, ref contains target but 2nd time it isn't. However, if I do a `docment.getElementById()` and check if that contains event.target, then it does. It looks as though the ref is outdated. – Mog0 Aug 18 '23 at 15:08
0

For this, you can give tabIndex=-1 to your modal div, and then you can use the onBlur event.

const [showModal, setShowModal] = useState(false);

return (
    <>
      <div
        className="homepage"
        style={{
          filter: showModal ? "blur(8px)" : "none",
          minHeight:"80vh",
        }}
      >
        <section
          className="homepage-hero"
          style={{ paddingBottom:"-2rem", minHeight:"100vh" }}
        >
          <div className="hero-body">
            <div className="container">
              <div className="columns">
                <div className="column ">
                  <h1>
                    <span className="heading">
                      Finance <br />
                      Scheme
                    </span>
                    <br />
                  </h1>
                  <p>
                   Lorem Ipsum
                  </p>
                  <div className="is-hidden-tablet">

                  </div>
                  <div className="button-group">
                    <button
                      style={{
                        fontWeight: "600",
                        padding: "0.75em 1.9em",
                        borderRadius: "0px",
                        color: "white",
                        backgroundColor: "#24ca7a",
                        border: "1px solid #24ca7a",
                        cursor: "pointer",
                      }}
                      onClick={() => setShowModal(true)}
                    >
                      Download
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </section>
      </div>
      {showModal && (
        <Modal
          modalId="signup-modal"
          onBlur={() => setShowModal(false)}
          canDismiss={false}
          modalWidth="70%"
          style={{position:"fixed", top:"20", minHeight:"50vh"}}
          tabIndex={-1}
          >        
          <div className="contact-us" style={{  margin:"20px"}}>
            <section className="contact-form"  >
              <div className="container">
                <div className="columns is-8 ">
                  <div
                    className="column is-half-desktop is-full-mobile container-content"
                    style={{ boxShadow: "none" }}
                  >
                    {submitted ? (
                      <div className="success">
                        <img src={confirmedIllus} alt="" />
                        <h2>Your details have been submitted successfully!</h2>
                      </div>
                    ) : (
                      <form onSubmit={handleOnSubmit} id="contact-form" >
                        <h1 className="heading" style={{ fontSize: "2rem" }}>
                          Your Details
                        </h1>
                        <br />
                        <div className="field">
                          <label className="label">Name</label>
                          <div className="control">
                            <input
                              className="input"
                              id="name"
                              value={name}
                              type="text"
                              placeholder="Your Full Name"
                              onChange={(e) => setName(e.target.value)}
                              required
                            />
                          </div>
                        </div>
                        <div className="field">
                          <label className="label">Email</label>
                          <div className="control ">
                            <input
                              className={`input ${isDanger}`}
                              id="email"
                              value={email}
                              type="email"
                              onChange={handleOnChange}
                              placeholder="Your Email"
                              required
                            />
                            {!validEmail && isDanger ? (
                              <span className="icon is-small is-right">
                                <i className="material-icons-round">warning</i>
                              </span>
                            ) : (
                              " "
                            )}
                          </div>
                          {!validEmail && isDanger ? (
                            <p className="help is-danger">{emailMessage}</p>
                          ) : (
                            ""
                          )}
                        </div>

                        <div className="field is-grouped submit-button-group">
                          <div className="control">
                            <button
                              style={{
                                cursor: !validEmail ? "not-allowed" : "pointer",
                              }}
                              className="button  submit-button"
                              id="submit-form"
                            >
                              Submit
                            </button>
                          </div>
                        </div>
                      </form>
                    )}
                  </div>
                  <div className="column is-half-desktop is-full-mobile " >
                    <img
                      src="/images/Ebook.svg"
                      className="is-hidden-mobile"
                      style={{ width: "70%", marginTop: "40%" }}
                    />
                    <div className=" font-blue bottom-text">
                      Fill your details to download the free <b> Ebook </b>
                    </div>
                  </div>
                </div>
              </div>
            </section>
          </div>
        </Modal>
      )}
    </>
  );
uditkumar01
  • 400
  • 5
  • 11
  • I haven't tried it manually but it should work. See this URL this will give you an idea of what to do https://stackoverflow.com/questions/57659459/onblur-for-div-element-in-react. – uditkumar01 Jun 17 '21 at 11:37
0

In your div className homepage you can use a condition when you click, if showModal is true --> setShowModal to false like

<div
 className="homepage"
 style={{
  filter: showModal ? "blur(8px)" : "none",
  minHeight: "80vh",
 }}
 onClick={() => showModal && setShowModal(false)}
>
Daphaz
  • 486
  • 4
  • 7
  • But this will also close the modal when we click inside the modal, the modal should only close when we click outside it – Cassandra Jun 17 '21 at 11:04
  • that a problem with your css, you need to put you modal in position absolute , center if you want with top: 50%;left: 50%; transform: translate(-50%,-50%); – Daphaz Jun 17 '21 at 11:09
  • or you can try to use z-index property style in your modal – Daphaz Jun 17 '21 at 11:17
  • I'm using both z-index property and the transform thing but it still won't work. You can take a look at my modal component. https://codesandbox.io/s/laughing-feynman-sqn9d – Cassandra Jun 17 '21 at 11:21
  • there doesn't seem to be any problem, but i don't know styled component, did you try to put a z-index : -1 on your div homepage what's happen ? Finally i think the problem is `the onClick in your ModalWrapper` – Daphaz Jun 17 '21 at 11:34