79

I'm making a modal in my React project that requires a class to be added to the body when the modal is open and removed when it is closed.

I could do this the old jQuery way by running some vanilla JavaScript which adds / removes a class, however this doesn't feel like the normal React philosophy.

Should I instead setState on my top level component to say whether the modal is open or closed? Even if I did this, as it's rendered into the div on the page it's still a side-effect to edit the body element, so is there any benefit for this extra wiring?

Null
  • 1,950
  • 9
  • 30
  • 33
Evanss
  • 23,390
  • 94
  • 282
  • 505
  • It might help to have a top-level container instead of directly manipulating the body tag. – Brian Dec 08 '17 at 02:42
  • 1
    I prefer `some vanilla javascript`, you can add class in `componentDidMount` and remove in `componentWillUnmount`, `mousewheel` is global and not `React philosophy`, still you use it – Josh Lin Dec 08 '17 at 03:27
  • Possible duplicate of [How to add or remove a className on event in ReactJS](https://stackoverflow.com/questions/28732253/how-to-add-or-remove-a-classname-on-event-in-reactjs) – garrettmac Nov 07 '18 at 18:32

5 Answers5

140

TL;DR use document.body.classList.add and document.body.classList.remove

I would have two functions that toggle a piece of state to show/hide the modal within your outer component.

Inside these functions I would use the document.body.classList.add and document.body.classList.remove methods to manipulate the body class dependant on the modal's state like below:

openModal = (event) => {
  document.body.classList.add('modal-open');
  this.setState({ showModal: true });
}
hideModal = (event) => {
  document.body.classList.remove('modal-open');
  this.setState({ showModal: false });
}
Sam Logan
  • 3,343
  • 2
  • 17
  • 13
47

With the new React (16.8) this can be solved with hooks:

import {useEffect} from 'react';

const addBodyClass = className => document.body.classList.add(className);
const removeBodyClass = className => document.body.classList.remove(className);

export default function useBodyClass(className) {
    useEffect(
        () => {
            // Set up
            className instanceof Array ? className.map(addBodyClass) : addBodyClass(className);

            // Clean up
            return () => {
                className instanceof Array
                    ? className.map(removeBodyClass)
                    : removeBodyClass(className);
            };
        },
        [className]
    );
}

then, in the component

export const Sidebar = ({position = 'left', children}) => {
    useBodyClass(`page--sidebar-${position}`);
    return (
        <aside className="...">
            {children}
        </aside>
    );
};
Viesturs Knopkens
  • 594
  • 1
  • 8
  • 16
Lukas Kral
  • 987
  • 13
  • 22
28

Actually you don't need 2 functions for opening and closing, you could use document.body.classList.toggle

const [isOpen, setIsOpen] = useState(false)
useEffect(() => {
  document.body.classList.toggle('modal-open', isOpen);
},[isOpen])
    
<button onCLick={()=> setIsOpen(!isOpen)}>Toggle Modal</button>
Bojan Mitic
  • 462
  • 5
  • 12
  • 1
    Replace isMobileOpen with isOpen: `document.body.classList.toggle('modal-open', isOpen);` – TheBosti Mar 19 '21 at 01:02
  • 4
    I would also add a cleanup function `return () => document.body.classList.remove('modal-open')` within the useEffect hook. – Yuya Aug 20 '21 at 14:41
1

Like what @brian mentioned, try having a top-level container component that wraps around your other components. (assuming you're not using redux in your app)

In this top-level component:

  1. Add a boolean state (eg. modalOpen) to toggle the CSS class
  2. Add methods (eg. handleOpenModal & handleCloseModal) to modify the boolean state.
  3. Pass the methods created above as props into your <Modal /> component
Justin Toh
  • 11
  • 1
  • 2
-7

ReactJS has an official React Modal component, I would just use that: https://github.com/reactjs/react-modal

swyx
  • 2,378
  • 5
  • 24
  • 39