How to prevent the background scrolling when a Portal is rendered?

56 Views Asked by At

I have component hierarchy that renders in the this manner: Claim->NewModal->Portal. When the portal component is rendered, I don't want the background body to be scrollable at all. Here is my code: Claim Component:

class Claim extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            showCalendarModal: false
        }
    }
    openModal = () => {
        this.setState({ showCalendarModal: true });
    };
    closeModal = () => {
        this.setState({ showCalendarModal: false });
    }
    render() {
        return (
            <React.Fragement>
                <a href="javascript: void(0);" onClick={openModal}> Open Modal</a>
                {this.state.showCalendarModal && (<NewModal isModalOpen={showCalendarModal} onClose={this.onModalClose} />)}
            </React.Fragement>
   );
    }
}

Here is my NewModal component:

import Portal from "./Portal";

class NewModal extends Component {
    constructor(props) {
        super(props);
        this.state = {
            isModalOpen: this.props.isModalOpen
        };
    }

  closeModal = () => {
      this.setState({ isModalOpen: false });
      this.props.onClose();
  };
  
  render() {
      const { isModalOpen } = this.state;
      console.log("NewModal is rendered");

      return (
          <Portal show={isModalOpen} onClose={this.closeModal} disableScroll={isModalOpen}>
              <p>A new modal is rendered here</p>
          </Portal>
      );
  }
}

Here is my Portal component:

import { createPortal } from "react-dom";

class Portal extends Component {
    constructor(props) {
        super(props);
        this.state = {
            mounted: false
        };
        this.portalRef = React.createRef();
    }

    componentDidMount() {
        this.portalRef.current = document.querySelector("#portal");
        this.setState({ mounted: true });
    }

    componentDidUpdate(prevProps) {
        const { show, disableScroll } = this.props;
        if (show !== prevProps.show || disableScroll !== prevProps.disableScroll) {
            this.handleScrollLock();
        }
    }

    handleScrollLock() {
        const html = document.querySelector("html");
        const body = document.querySelector("body");
        const { show, disableScroll } = this.props;

        if (disableScroll && show) {
            body.style.overflow = "hidden";
            html.style.overflow = "hidden";
        } else if (disableScroll && !show) {
            body.style.overflow = null;
            html.style.overflow = null;
        }
    }

    componentWillUnmount() {
        if (this.props.disableScroll) {
            const html = document.querySelector("html");
            const body = document.querySelector("body");
            body.style.overflow = null;
            html.style.overflow = null;
        }
    }

    render() {
        const { children, show, onClose } = this.props;
        const { mounted } = this.state;

        return mounted && this.portalRef.current
            ? createPortal(
                <React.Fragment>{show ? (
                    <React.Fragment>
                      <div onClick={onClose}/>
                    <div>{children}</div></React.Fragment>
                ) : null}</React.Fragment>,
                this.portalRef.current
            )
            : null;
    }

When I console log the prevProps and this.props in componentDidUpdate, I get the same values for disableScroll and show. And hence it does not executes the handleScrollLock method. The values for disableScroll and show should be true only when the portal is rendered, so the prevProps should have their values are false. (Correct me if I am wrong here.) How can I achieve this? This worked fine when I used useEffect hook in an another repository. Here is the implementation for it:

useEffect(() => {
        portalRef.current = document.querySelector('#portal');
        setMounted(true);
    }, []);

useEffect(() => {
    const html = document.querySelector('html');
    const body = document.querySelector('body');
    if (disableScroll && show) {
        body.style.overflow = 'hidden';
        html.style.overflow = 'hidden';
    }
    else if (disableScroll && !show) {
        body.style.overflow = null;
        html.style.overflow = null;
    }
    return () => {
        if (disableScroll) {
            const html = document.querySelector('html');
            const body = document.querySelector('body');
            body.style.overflow = null;
            html.style.overflow = null;
        }
    };
}, [ show, disableScroll ]);

I can't use hooks here as the react version in my repository is an old one and I can't change it. Can anyone point out what I am missing out here?

0

There are 0 best solutions below