skip to Main Content

I am writing a modal component in React. The logic is simple, first pass the variable visible as props to the component modal. Then the external component updates the state of the modal by controlling visible. But I got an issue where modal didn’t turn off after the external component set visible to false, and it didn’t seem like setState was in effect.

I have provided the complete demo code, you can see the log that the visible object is not change as I set a new Object to it.

Code sandbox click to see demo

I hope you can help me look at this problem, thank you very much!

export default function () {
  const [visible, setVisible] = useState(() => ({
    m1: false,
    m2: false,
    m3: false
  }));

  const close = useCallback(() => {
    const s = { ...visible, m1: false };
    console.log("should be: ", s);
    setVisible(s);
  }, []);

  useEffect(() => {
    console.log("newVal: ", visible);
  }, [visible]);

  const props = {
    components: [
      {
        component: (
          <button
            onClick={() => {
              setVisible({ ...visible, m1: true });
            }}
          >
            open Modal1
            <Modal visible={visible.m1} close={close}>
              this is a modal
            </Modal>
          </button>
        )
      }
    ]
  };

  return <IntroComponent {...props} />;
}

enter image description here

2

Answers


  1. You need to call event.stopPropagation() in order to prevent the button event from opening modal message up again.

     const close = useCallback((event) => {
        const s = { ...visible, m1: false };
        console.log("should be: ", s);
        setVisible(s);
        event.stopPropagation();
      }, []);
    
    Login or Signup to reply.
  2. This is because your Modal is not actually a Modal. It is a child of <button {...}/>, likewise when button is clicked, it will make m1: false, but the event bubbles up to the Modal onClick handler making m1: true again.

              <button
                onClick={() => {
                  console.log("Activated!");
                  // When you click on the Modal component, this will also trigger.
                  setVisible({ ...visible, m1: true });
                }}
              >
                open Modal1
                <Modal visible={visible.m1} close={close}>
                  this is a modal
                </Modal>
              </button>
    

    When you change the DOM structure you can see it works just fine. (side note: <> is syntactic sugar for fragments). So this will work:

    component: (
              <>
                <button
                onClick={() => {
                  setVisible({ ...visible, m1: true });
                }}
                >
                  open Modal1
                  
                </button>
                <Modal visible={visible.m1} close={close}>
                  this is a modal
                </Modal>
              </>
            )
    

    @John has the right idea, and while it technically solves the issue, it is more a workaround than an actual desirable outcome. See Q: When is good practice to use stopPropagation()?

    Also when I see language like Modal, for your consideration is the ReactPortal. However, it is a complex topic that requires some understanding about how the DOM and VDOM interact, and beyond the scope of this Q/A.

    View CodeSandbox

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search