Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] - Modal will not close even though isOpen is false (behavior not matching state) #4954

Open
Kismet333 opened this issue Feb 28, 2025 · 4 comments

Comments

@Kismet333
Copy link

Kismet333 commented Feb 28, 2025

HeroUI Version

2.7.2

Describe the bug

I am modifying the isOpen state of a modal within a useEffect hook that has some redux states as a dependencies and I find that a modal remains open even though the isOpen state is set to false. When this occurs, I try to hide the modal by clicking my button that has the "hide" function tied to it many times, but it does not hide. I'm assuming that this is due to the fact that isOpen is already set to false and there is no state change when I click the button. I even added some "onChange" logging to verify and I found that both the onChange logging as well as the logging of the isOpen state show that the isOpen state is set to false even though the modal continues to show. I am wondering if this bug is caused due to me changing the visibility state rapidly within an useEffect hook and since the visibility might change a few times in quick succession? I debugged and saw that there is no loop or other continuous state modifications for the modal visibility going on.

An important note: I added a throttling mechanism for testing purposes which keeps all state updates in a queue, only allowing one state update be made every 500ms and it fixed the bug. Seems the issue is the rate of state updates?

There is a bunch of code in my component in which I saw this bug but here is all the relevant items to this bug:

const user = useSelector((state) => state.user.user); 
const projects = useSelector((state) => state.project.projects); 
const initialCreateProjectModalState = !!projects && projects.length === 0;
const [justDeletedAProject, setJustDeletedAProject] = useState(false);
    const { isOpen: isCreateFirstProjectModalShowing, onOpen: discloseShowCreateFirstProjectModal, onClose: discloseHideCreateFirstProjectModal, onOpenChange: onCreateFirstProjectOpenChange } = useDisclosure({
      defaultIsOpen: initialCreateProjectModalState,  // Set initial value for isOpen here
      onChange: (isOpen) => {
        console.log('Modal visibility changed: ', isOpen)},
    });

  useEffect(() => {
    if (!justDeletedAProject && !!user && !!projects && projects.length === 0) {
      discloseShowCreateFirstProjectModal();
    } else {
      discloseHideCreateFirstProjectModal();
    }
  }, [user, projects, justDeletedAProject]);

useEffect(() => {
    if (deleteProjectStatus === COMPLETED) {
      if (!!!deleteProjectError) {
        setJustDeletedAProject(true);
        dispatch(projectActions.deleteProjects([deleteProjectResponse]));
        if (activeProjectId !== null && activeProjectId === deleteProjectResponse.id) {
          const updatedUser = {...user, activeProjectId: null};
          sendUpdateUserRequest(updatedUser);
        }
        props.setInfoModalText(SUCCESSFUL_PROJECT_DELETE_MSG);
        props.setIconToDisplay(CHECKMARK);
        props.setIsInfoModalShowing(true);
      } else {
        props.setErrorModalText("Error: Could not delete project.");
        props.discloseShowErrorModal();
        console.warn('deleteProjectError: ' + deleteProjectError);  
      }
      props.discloseHideBasicLoadingModal();
    }
  }, [deleteProjectStatus, deleteProjectError]);


const createFirstProjectBodyJsx = <React.Fragment>
  <TriangleCautionIcon size={35} fill='black'/>
          <p>
            {PROJECT_CREATION_PROMPT}
          </p>
</React.Fragment>;

const createFirstProjectFooterJsx = <React.Fragment>
  <Button color="danger" variant={'flat'} onPress={discloseHideCreateFirstProjectModal}>
No
</Button>
<Button color="primary" onPress={createFirstProject}>
Yes
</Button>
  </React.Fragment>;


return <NextUIModal size={'md'} header={null} body={createFirstProjectBodyJsx} footer={createFirstProjectFooterJsx} 
     isOpen={isCreateFirstProjectModalShowing} onOpenChange={onCreateFirstProjectOpenChange} ariaLabel={"Create First Project Modal"}/>

My NextUIModal component:

import React from 'react';
import styles from "./NextUIModal.module.css";
import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from "@heroui/react";

const NextUIModal = ({
    isDismissable = true,
    hideCloseButton = false,
    size = '5xl',
    backdrop = 'opaque',
    headerClass = 'justify-center',
    bodyClass = 'justify-center items-center',
    footerClass = 'justify-center',
    ...props
}) => {

  const classNames = React.useMemo(
    () => ({
      base: ["border-5 border-blue-400"],
      // base: [],
      // backdrop: [],
      // closeButton: [],
      // header: [],
      // body: [],
      // footer: [],
    }),
    [],
  );

    
    const header  = !!props.header ? <ModalHeader className={headerClass}>
            {props.header}
    </ModalHeader> : null;

    const footer = !!props.footer ? <ModalFooter className={footerClass}>
    {props.footer}
    </ModalFooter> : null;

    return (
        <Modal
        backdrop={backdrop}
        classNames={!!props.tailwindClasses ? props.tailwindClasses : classNames}
        hideCloseButton={hideCloseButton}
        isDismissable={isDismissable}
        closeButton={props.closeButton}
        aria-labelledby={props.ariaLabel}
        onOpenChange={(isOpen) => {
          props.onOpenChange(isOpen);  // Custom onChange logic if needed
        }}
        isOpen={props.isOpen}
        size={size}
      >
        <ModalContent>
          {(onClose) => (
            <>
        {header}
        <ModalBody className={bodyClass}>
            {props.body}
        </ModalBody>
        {footer}
            </>
          )}
        </ModalContent>
      </Modal>
    );
};
export default NextUIModal;

Your Example Website or App

No response

Steps to Reproduce the Bug or Issue

  1. I delete a project from my app.
  2. This causes my the lower useEffect to trigger which update both "justDeletedAProject" state and "projects" redux state.
  3. The update in states causes the upper useEffect to trigger, which manages the visibility of the Modal to be triggered which then causes a bug. As I mentioned in the description of the bug, for my testing I added some queueing mechanism to this useEffect to throttle the rate at which the visibility of the modal is being updated and it fixed the bug

I would also suspect if the visibility of a Modal is toggled multiple times in any other scenario, this would also reproduce the bug.

Expected behavior

Modal visibility behavior to match the isOpen state.

Screenshots or Videos

No response

Operating System Version

windows

Browser

Chrome

@wingkwong
Copy link
Member

please provide a minimal reproducible sandbox instead.

@MCD4U
Copy link

MCD4U commented Mar 8, 2025

I am facing a similar issue too with the latest version v2.7.5 where isOpen can sometimes be desynced with the actual modal behaviour. Here is minimal reproducible code:

import { Modal, ModalBody, ModalContent } from "@heroui/modal";
import { useEffect, useState } from "react";

function App() {
    const [show, setShow] = useState(true);

    useEffect(() => {
        setShow(false);
    }, []);

    return (
        <Modal isOpen={show}>
            <ModalContent>
                <ModalBody>Hello!</ModalBody>
            </ModalContent>
        </Modal>
    );
}

export default App;

The expected behaviour is that the modal should be closed. However by repeatedly refreshing the page, you will notice that the modal will remain open most of the time.

@Kismet333
Copy link
Author

Ty @MCD4U for providing that code. I got busy and my workaround seems to be working.

I would assume any code that causes the state to change rapidly can cause this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants