55

So I render a component via React within my html like so:

 <html>
  <body>
    <div id=app>${appHtml}</div>
    <script src="/bundle.js"></script>
  </body>
</html>

Within my app I have a burger button, that onClick goes full screen.

However, the body is scrollable. I'd normally add a class to the body tag and make it overflow: hidden to prevent this. However, my react component is rendered within the body tag so I have no control over toggling classes based on a click within a react component.

Has anyone got any ideas/advice on how i'd achieve what i'm looking for.

Thanks!

2

9 Answers 9

92

"I have no control over toggling classes based on a click within a react component."

Not necessarily true!

It's good that you're thinking in a "React-ful" way and wary about modifying the DOM. The main reason you want to avoid doing DOM manipulation is because it causes conflicts between what React is trying to render and the unknown changes you might be trying to make. But in this case you're not manipulating the DOM that React is rendering, you're manipulating its parent. In that case you would be totally fine doing something like this:

document.body.style.overflow = "hidden"

Or

document.body.classList.add("no-scroll")

Or whatever works. You're totally safe because React only renders the DOM within #app and doesn't care about what happens in its parent. In fact many apps and websites use React in only a small part of the page, to render a single component or widget, instead of an entire app.

That aside, there is an even better, more "React-ful" way to do what you want. Simply restructure your app in such a way that the scrolling container is within your React app instead of body. The structure might look something like this:

<html>
  <body>
    <div id="app">
      <div id="scroll-container">
        <!-- the rest of your app -->
      </div>
    </div>
  </body>
</html>

Make body overflow hidden, and body and #app fill the entire screen, and you can control whether #scroll-container allows scrolling or not entirely within React.

2
  • Thanks man! I actually thought react would just complain if you tried to change anything outside it's component. I never knew you could still do that. Commented Oct 10, 2016 at 20:29
  • 1
    there's no dom on nextjs Commented Sep 2, 2021 at 18:13
8

The above doesn't work for iOS mobile.

body-scroll-lock uses a combination of CSS and JS to make it work for all devices, whilst maintaining scrollability of a target element (eg. modal).

ie. for iOS, need to detect when the bottom or top of a target element is reached, and then stop scrolling completely

5
  • 3
    How does this answer the OP's question?
    – Raul Sauco
    Commented Feb 18, 2018 at 11:55
  • Does body-scroll-lock let you to prevent a parent element to scroll when its child has been scrolled all way down to the bottom or to the top?
    – tonix
    Commented Jul 8, 2019 at 10:38
  • @RaulSauco I had to do a bit of playing around with it myself, check out my answer in this question. Commented Mar 29, 2021 at 12:54
  • tried body-scroll-lock does not worked for me
    – János
    Commented Dec 31, 2022 at 17:09
  • stackoverflow.com/questions/74971048/…
    – János
    Commented Dec 31, 2022 at 17:14
5

use React.useEffect() hook to disable scrolling on a specific page then cleanup. e.g

useEffect(() => {
    document.body.style.overflow = "hidden";
    return () => {
        document.body.style.overflow = "scroll"
    };
}, []);
4

Here is how to use the body-scroll-lock library with reactjs...

import React, {useState} from 'react';
import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock';

// Components
import Hamburger from './HeaderComponents/Hamburger.js';
import MegaDropdown from './HeaderComponents/MegaDropdown.js';

const Header = props => {
  // variables
  const [isSideNavShown, setIsSideNavShown] = useState(false);
  const [isDropdownShowing, setIsDropdownShowing] = useState(false);

  isDropdownShowing ? disableBodyScroll(document) : enableBodyScroll(document)

  return (
    <header className="header">
      <Hamburger isDropdownShowing={isDropdownShowing} setIsDropdownShowing={setIsDropdownShowing} />
      <MegaDropdown isDropdownShowing={isDropdownShowing} setIsDropdownShowing={setIsDropdownShowing} />
    </header>
  )
}

export default Header;

in particular the line isDropdownShowing ? disableBodyScroll(document) : enableBodyScroll(document)

This allows for when the content in my dropdown fills up to the point where it needs to add a scroll to it, the window(body) scroll is disabled while the dropdown is showing(covering my whole screen). So even though the window(body) scroll is disabled, I can still scroll through my dropdown.

2

a very simple way to achieve this in a functional components with Hooks (React 16.8 +) is using useEffect to perform side effects.

  useEffect(() => {
    const html = document.querySelector("html");
    if (html) {
      html.style.overflow = state.isMenuOpen ? "hidden" : "auto";
    }
  }, [state.isMenuOpen]); // condition to watch to perform side effect
1

Here's my two cents: you can set the height of div containing your content to 100% ( height: 100%) Or in this case body { height: 100%}

1

I would suggest using css position: "sticky" , but I guess that's not what is questioned here. But it worked for me. The other solutions are better I guess.

0
0

Wrote a tiny hook to handle the body scroll.

import { useState, useEffect } from "react";

export default function useSetBodyScroll() {
  const [bodyScroll, setBodyScroll] = useState(true);

  useEffect(() => {
    const resetOnResize = () => {
      if (window.innerWidth <= 1023) document.body.style.overflow = "hidden";
      if (window.innerWidth >= 1024) document.body.style.overflow = "scrolls";
    };

    if (!bodyScroll) {
      document.body.style.overflow = "hidden";
    } else {
      document.body.style.overflow = "scroll";
      window.addEventListener("resize", resetOnResize);
    }

    return () => {
      window.removeEventListener("resize", resetOnResize);
    };
  }, [bodyScroll]);

  return setBodyScroll;
}
0

On Next.JS you can use:

const html = document.querySelector('html');
if (html) {
  html.style.overflow = 'hidden';
}

And you can reset it with:

const html = document.querySelector('html');
if (html) {
  html.style.overflow = 'auto';
}
1
  • This worked for me. As far as I can tell there is no way to dynamically update the styling of the <html> tag of the root layout in the Next.js app router unless you wanted to pass a ref all the way down the the component that needed it. @Gipfeli would this affect other pages that use the same root layout? The useEffect hook might be important here. Commented Mar 19, 2024 at 16:12

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.