70

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,940
  • 9
  • 26
  • 32
Evanss
  • 20,529
  • 79
  • 252
  • 460
  • 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

122

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
  • 2,933
  • 2
  • 16
  • 12
42

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
  • 554
  • 1
  • 8
  • 16
Lukas Kral
  • 937
  • 13
  • 21
19

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
  • 353
  • 3
  • 10
  • 1
    Replace isMobileOpen with isOpen: `document.body.classList.toggle('modal-open', isOpen);` – TheBosti Mar 19 '21 at 01:02
  • 1
    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
0

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
JustinToh
  • 1
  • 1
  • 1
-6

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

swyx
  • 1,941
  • 5
  • 21
  • 35