disable scrolling on specific page - css

Is there any way to disable scroll on one single page? I tried to set overflow: hidden on the specific page but that didn't work. I have to set it on the body in index.css to make it work but that obviously disable scroll on all pages. So the only way to do it that comes to my mind is to set CSS class conditionally on the body. Is there any way to conditionally set CSS class in index.js based on the value from a redux store or is there any other way?
my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom'
import {Provider} from 'react-redux'
import {createStore,applyMiddleware,compose,combineReducers} from "redux";
import thunk from 'redux-thunk'
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import authReducer from './store/reducers/auth'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers({
auth: authReducer
})
const store = createStore(rootReducer,
composeEnhancers(applyMiddleware(thunk)));
const app =(
<Provider store={store}>
<BrowserRouter>
<App/>
</BrowserRouter>
</Provider>
)
ReactDOM.render(
<React.StrictMode>
{app}
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();

If you wanted to set a style on body, you can just run the below code on the page, which will disable scrolling.
document.body.style.overflow='hidden'
Then to re-enable:
document.body.style.overflow='auto'
Only downside is that is isn't very React-like.

Simple solution might be to define specific class to apply overflow: hidden and apply it on document.body whenever you want (for example after explicit component mount).
.overflow-hidden {
overflow: hidden;
}
On mount of specific page call:
document.body.classList.add("overflow-hidden");
or you can directly assign property
document.body.style.overflow = "hidden";

You can wrap your component in another div, and give that wrapper div the overflow:hidden; style, possibly along with a max-height: 70vh; to make sure it doesn't go over the end of the page.
div {
padding: .5rem;
margin: .5rem
}
.no-scroll-wrapper {
max-height: 70vh;
overflow: hidden;
background-color: darkgrey;
padding: 1rem;
}
.large-inner {
height: 1000px;
background-color: grey
}
<body>
<div class="no-scroll-wrapper">
wrapper element disables scrolling
<div class="large-inner">
Content here, very long div, but you can't see the end of it.
</div>
</div>
</body

A simple solution might be to define a specific class to apply overflow: hidden and apply it on the document. body whenever you want and remove the class when you leave the page.
.overflow-hidden {
overflow: hidden;
}
Attach class on the body in the specific page and remove when you live the page, so it won't affect other pages
useEffect(() => {
document.body.classList.add("overflow-hidden");
return () => {
document.body.classList.remove("overflow-hidden");
};
}, []);
This will simply remove the class from the body when you leave the page.

Adding to Luke Storry's and midnightgamer's amazing answer. You can use a custom react hook with useEffect instead of having to add document.body.style.overflow='auto' in every page to re-enable scroll.
import { useEffect } from 'react'
const useOverFlowHidden = () => {
useEffect(() => {
document.body.style.overflow = 'hidden'
return () => {
document.body.style.overflow = 'auto' // cleanup or run on page unmount
}
}, [])
}
export default useOverFlowHidden
Usage:
const Page = () => {
useOverFlowHidden(); // use on pages you want to disable overflow/scroll
return <></>;
}

I use body-scroll-lock npm package:
import {disableBodyScroll,enableBodyScroll,clearAllBodyScrollLocks} from "body-scroll-lock"
const YourPage=(props)=>{
const ref=useRef(null)
useEffect(() => {
if (ref.current) {
// you might add a condition to lock the scroll
if (conditionIsTrue) {
disableBodyScroll(ref.current)
} else {
enableBodyScroll(ref.current)
}
}
return () => {
clearAllBodyScrollLocks()
}
},[addDependencyHere])
return(
<div ref={ref}>
....
</>
)
}
from the above npm package's documentation, you can also use this approach with vanilla js. It is not recommended to access document object in react.js directly
document.body.ontouchmove = (e) => {
e.preventDefault();
return false;
on touch screen devices you can set this css class conditionally to prevent scrolling
touch-action:none

Related

How to dynamically track width/height of div in React.js

I need a help with my work task: I have some div block with following styles:
.main-div {
width: 500px;
height: 400px;
border: 1px solid #000;
resize: both;
overflow: hidden;
}
And some React Component:
import React, { useState } from "react";
export const SomeComponent = () => {
const [width, setWidth] = useState();
const [height, setHeight] = useState();
return (
<div className="main-div">
<p>Block width: {width}, height: {height} </p>
</div>
)
}
How can I get dynamically width/height of resizing div? I mean i have to keep track of the block dimension in the process of resizing.
You could combine ResizeObserver, useEffect, and useRef:
ResizeObserver allows you to observe a HTML element and to execute an event handler every time it gets resized;
useEffect is executed right after the component mounted, so it is the right place to attach the observer to your div, and to disconnect it when the component is going to unmount via the returned function;
useRef creates a reference through to a child.
import React, { useState, useEffect, useRef } from "react";
export const SomeComponent = () => {
const [width, setWidth] = useState();
const [height, setHeight] = useState();
// useRef allows us to "store" the div in a constant,
// and to access it via observedDiv.current
const observedDiv = useRef(null);
// we also instantiate the resizeObserver and we pass
// the event handler to the constructor
const resizeObserver = new ResizeObserver(handleElementResized);
useEffect(() => {
// the code in useEffect will be executed when the component
// has mounted, so we are certain observedDiv.current will contain
// the div we want to observe
resizeObserver.observe(observedDiv.current);
// if useEffect returns a function, it is called right before the
// component unmounts, so it is the right place to stop observing
// the div
return function cleanup() {
resizeObserver.disconnect();
}
}
const handleElementResized = () => {
if(observedDiv.current.offsetWidth !== width) {
setWidth(observedDiv.current.offsetWidth);
}
if(observedDiv.current.offsetHeight !== height) {
setHeight(observedDiv.current.offsetHeight);
}
}
return (
<div className="main-div" ref={observedDiv}>
<p>Block width: {width}, height: {height} </p>
</div>
)
}
Sources
ResizeObserver:
API
Browser compatibility
useEffect hook
useRef hook

How can I test css properties for a React component using react-testing-library?

The philosophy behind the react-testing-library makes sense to me, but I am struggling to apply it to css properties.
For example, let's say I have a simple toggle component that shows a different background color when clicked:
import React, { useState } from "react";
import "./Toggle.css";
const Toggle = () => {
const [ selected, setSelected ] = useState(false);
return (
<div className={selected ? "on" : "off"} onClick={() => setSelected(!selected)}>
{selected ? "On" : "Off"}
</div>
);
}
export default Toggle;
.on {
background-color: green;
}
.off {
background-color: red;
}
How should I test this component? I wrote the following test, which works for inline component styles, but fails when using css classes as shown above.
import React from "react";
import { render, screen, fireEvent } from "#testing-library/react";
import Toggle from "./Toggle";
const backgroundColor = (element) => window.getComputedStyle(element).backgroundColor;
describe("Toggle", () => {
it("updates the background color when clicked", () => {
render(<Toggle />);
fireEvent.click(screen.getByText("Off"));
expect(backgroundColor(screen.getByText("On"))).toBe("green");
});
});
So that's not what unit or integration test frameworks do. They only test logic.
If you want to test styling then you need an end-to-end/snapshot testing framework like Selenium.
For making sure the styling is okay, I prefer snapshot testing. How about firing the event and taking snapshots for both states/cases. Here is what it would look like:
import React from 'react'
import {render} from '#testing-library/react'
it('should take a snapshot when button is toggled', () => {
const { asFragment } = render(<App />)
// Fire the event
expect(asFragment(<App />)).toMatchSnapshot()
})
});

How to override classes using makeStyles and useStyles in material-ui?

Consider a component that renders a button and says this button should have a red background and a yellow text color. Also there exists a Parent component that uses this child but says, the yellow color is fine, but I want the background color to be green.
withStyles
No problem using the old withStyles.
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import { Button } from "#material-ui/core";
const parentStyles = {
root: {
background: "green"
}
};
const childStyles = {
root: {
background: "red"
},
label: {
color: "yellow"
}
};
const ChildWithStyles = withStyles(childStyles)(({ classes }) => {
return <Button classes={classes}>Button in Child withStyles</Button>;
});
const ParentWithStyles = withStyles(parentStyles)(({ classes }) => {
return <ChildWithStyles classes={classes} />;
});
export default ParentWithStyles;
https://codesandbox.io/s/passing-classes-using-withstyles-w17xs?file=/demo.tsx
makeStyles/useStyles
Let's try the makeStyles/useStyles instead and follow the guide Overriding styles - classes prop on material-ui.com.
import React from "react";
import { makeStyles } from "#material-ui/styles";
import { Button } from "#material-ui/core";
const parentStyles = {
root: {
background: "green"
}
};
const childStyles = {
root: {
background: "red"
},
label: {
color: "yellow"
}
};
// useStyles variant does NOT let me override classes
const useParentStyles = makeStyles(parentStyles);
const useChildStyles = makeStyles(childStyles);
const ChildUseStyles = ({ classes: classesOverride }) => {
const classes = useChildStyles({ classes: classesOverride });
return (
<>
<Button classes={classes}>Button1 in Child useStyles</Button>
<Button classes={classesOverride}>Button2 in Child useStyles</Button>
</>
);
};
const AnotherChildUseStyles = props => {
const classes = useChildStyles(props);
return (
<>
<Button classes={classes}>Button3 in Child useStyles</Button>
</>
);
};
const ParentUseStyles = () => {
const classes = useParentStyles();
return <>
<ChildUseStyles classes={classes} />
<AnotherChildUseStyles classes={classes} />
</>
};
export default ParentUseStyles;
https://codesandbox.io/s/passing-classes-using-usestyles-6x5hf?file=/demo.tsx
There seems no way to get the desired effect that I got using withStyles. A few questions, considering I still want the same effect (green button yellow text) using some method of classes overriding (which seemed to make sense to me before).
How is my understanding wrong about how to pass classes as means to override parts of them using useStyles?
How should I approach it alternatively?
And if I'm using the wrong approach, why is material-ui still giving me a warning when the parent has something in the styles that the child doesn't have?
the key something provided to the classes prop is not implemented in [Child]
Is the migration from the old approach (withStyles) vs the new approach documented somewhere?
Btw, I'm aware of this solution but that seems cumbersome when you have too much you want to override.
const useStyles = makeStyles({
root: {
backgroundColor: 'red',
color: props => props.color, // <-- this
},
});
function MyComponent(props) {
const classes = useStyles(props);
return <div className={classes.root} />;
}
withStyles has very little functionality in it. It is almost solely a wrapper to provide an HOC interface to makeStyles / useStyles. So all of the functionality from withStyles is still available with makeStyles.
The reason you aren't getting the desired effect is simply because of order of execution.
Instead of:
const useParentStyles = makeStyles(parentStyles);
const useChildStyles = makeStyles(childStyles);
you should have:
const useChildStyles = makeStyles(childStyles);
const useParentStyles = makeStyles(parentStyles);
The order in which makeStyles is called determines the order of the corresponding style sheets in the <head> and when specificity is otherwise the same, that order determines which styles win (later styles win over earlier styles). It is harder to get that order wrong using withStyles since the wrapper that you are using to override something else will generally be defined after the thing it wraps. With multiple calls to makeStyles it is easier to do an arbitrary order that doesn't necessarily put the overrides after the base styles they should impact.
The key to understanding this is to recognize that you aren't really passing in overrides, but rather a set of classes to be merged with the new classes. If childClasses.root === 'child_root_1' and parentClasses.root === 'parent_root_1', then the merged result is mergedClasses.root === 'child_root_1 parent_root_1' meaning any elements that have their className set to mergedClasses.root are receiving both CSS classes. The end result (as far as what overrides what) is fully determined by CSS specificity of the styles in the two classes.
Related answers:
Material UI v4 makeStyles exported from a single file doesn't retain the styles on refresh
Internal implementation of "makeStyles" in React Material-UI?
In Material-ui 4.11.x while creating styles using makeStyles wrap the enclosing styles with createStyles, and this style will have highest priority than the default one.
const useStyles = makeStyles((theme: Theme) =>
createStyles({
backdrop: {
zIndex: theme.zIndex.drawer + 1,
color: '#fff',
},
}),
);
You could try removing the createStyles and see the difference.
code source from https://material-ui.com/components/backdrop/
One way to achieve this using withStyles is the following and can be helpful to override css classes.
Supposing that you want to override a class called ".myclass" which contains "position: absolute;":
import { withStyles } from '#material-ui/styles';
const styles = {
"#global": {
".myClass": {
position: "relative",
}
}
};
const TestComponent = (props) => (
<>
<SomeComponent {...props}>
</>
);
export default withStyles(styles)(TestComponent);
After doing this, you override the definition of .myClass defined on <SomeComponent/> to be "position: relative;".

How to switch between themes in Ant design v4 dynamically?

I'd like to implement switching between dark/light theme dynamically with Ant design v4.
It's possible to customize the theme with other CSS/LESS imports as it's written here:
https://ant.design/docs/react/customize-theme#Use-dark-theme
But I'm not sure how to switch between those themes dynamically from the code. I have a variable in my React app (darkMode) which indicates if the dark theme is currently used. I have to provide correct CSS files when this variable is changed. But I can't import CSS dynamically only when some condition is fulfilled, because it's not way how the imports work.
I tried to do something messy with require like in the following code, but it's a very very bad approach and it's still not working properly (because CSS is injected but probably not withdrawn.
):
const Layout = () => {
...
useEffect(() => {
if (darkMode === true) {
require("./App.dark.css")
} else {
require("./App.css")
}
}, [darkMode])
return (
<Home />
)
}
It should be possible to switch themes somehow because it's already implemented in Ant design docs (https://ant.design/components/button/):
Do you have any idea how to do it?
Thanks!
This is what I am using for now -
PS -
I don't know if this will yield optimal bundle size.
changing theme results in a page reload.
make a folder called "themes" - it would have 6 files -> dark-theme.css, dark-theme.jsx, light-theme.css, light-theme.jsx, use-theme.js, theme-provider.jsx. Each of them is described below.
dark-theme.css
import "~antd/dist/antd.dark.css";
dark-theme.jsx
import "./dark-theme.css";
const DarkTheme = () => <></>;
export default DarkTheme;
light-theme.css
#import "~antd/dist/antd.css";
light-theme.jsx
import "./light-theme.css";
const LightTheme = () => <></>;
export default LightTheme;
use-theme.js A custom hook that different components can use -
import { useEffect, useState } from "react";
const DARK_MODE = "dark-mode";
const getDarkMode = () => JSON.parse(localStorage.getItem(DARK_MODE)) || false;
export const useTheme = () => {
const [darkMode, setDarkMode] = useState(getDarkMode);
useEffect(() => {
const initialValue = getDarkMode();
if (initialValue !== darkMode) {
localStorage.setItem(DARK_MODE, darkMode);
window.location.reload();
}
}, [darkMode]);
return [darkMode, setDarkMode];
};
theme-provider.jsx
import { lazy, Suspense } from "react";
import { useTheme } from "./use-theme";
const DarkTheme = lazy(() => import("./dark-theme"));
const LightTheme = lazy(() => import("./light-theme"));
export const ThemeProvider = ({ children }) => {
const [darkMode] = useTheme();
return (
<>
<Suspense fallback={<span />}>
{darkMode ? <DarkTheme /> : <LightTheme />}
</Suspense>
{children}
</>
);
};
change index.js to -
ReactDOM.render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>,
document.getElementById("root")
);
now, in my navbar suppose I have a switch to toggle the theme. This is what it would look like -
const [darkMode, setDarkMode] = useTheme();
<Switch checked={darkMode} onChange={setDarkMode} />
you must create 2 components
the first one :
import './App.dark.css'
const DarkApp =() =>{
//the app container
}
and the second :
import './App.light.css'
const LightApp =() =>{
//the app container
}
and create HOC to handle darkMode like this :
const AppLayout = () =>{
const [isDark , setIsDark] = useState(false);
return (
<>
{
isDark ?
<DarkApp /> :
<LightApp />
}
</>
)
}
ANTD internally uses CSS variables for the "variable.less" file. We can override these variables to make the default variables have dynamic values at runtime.
This is one way it can be achieved:
app-colors.less file:
:root{
--clr-one: #fff;
--clr-two: #000;
--clr-three: #eee;
}
// Dark theme colors
[data-thm="dark"]{
--clr-one: #f0f0f0;
--clr-two: #ff0000;
--clr-three: #fff;
}
We can now override the default ANTD variables in antd-overrides.less file:
#import <app-colors.less file-path>;
#primary-color: var(--clr-one);
#processing-color: var(--clr-two);
#body-background: var(--clr-three);
We usually import the antd.less file to get the antd styles, We would change that to "antd.variable.less"(to use css variables).
index.less file:
#import "~antd/dist/antd.variable.less";
#import <antd-overrides.less file-path>;
Now we need to toggle the "data-thm" attribute on a parent container(body tag recommended) to change the set of CSS variables that get used.
const onThemeToggle = (themeType) => {
const existingBodyAttribute = document.body.getAttribute("data-thm");
if (themeType === "dark" && existingBodyAttribute !== "dark") {
document.body.setAttribute("data-thm", "dark");
} else if (themeType === "light" && existingBodyAttribute) {
document.body.removeAttribute("data-thm");
}
};
The above piece of code can be called on a Theme toggle button or during component mount.
In Ant's example one suggestion is to import your "dark mode" CSS or LESS file into your main style sheet.
// inside App.css
#import '~antd/dist/antd.dark.css';
Instead of trying to toggle stylesheets, the "dark" styles are combined with base styles in one stylesheet. There are different ways to accomplish this, but the common pattern will be:
have a dark-mode selector of some sort in your CSS
put that selector in your HTML
have a way to toggle it on or off.
Here is a working example:
https://codesandbox.io/s/compassionate-elbakyan-f7tun?file=/src/App.js
In this example, toggling the state of darkMode will add or remove a dark-mode className to the top level container.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [darkMode, setDarkMode] = useState(false);
return (
<div className={`App ${darkMode && "dark-mode"}`}>
<label>
<input
type="checkbox"
checked={darkMode}
onChange={() => setDarkMode((darkMode) => !darkMode)}
/>
Dark Mode?
</label>
<h1>Hello CodeSandbox</h1>
</div>
);
}
If darkMode is true, and the dark-mode className is present, those styles will be used:
h1 {
padding: 0.5rem;
border: 3px dotted red;
}
.dark-mode {
background: black;
color: white;
}
.dark-mode h1 {
border-color: aqua;
}
Ant Design newly start to support dynamic theme support. But its on experimental usage. You can find details on this link.
Conditional require won't block using previously required module. So, whenever your condition matches the require will available in your app. So, your both required module will be used. Instead of requiring them, insert stylesheet and remove to toggle between them:
const head = document.head
const dark = document.createElement('link')
const light = document.createElement('link')
dark.rel = 'stylesheet'
light.rel = 'stylesheet'
dark.href = 'antd.dark.css'
light.href = 'antd.light.css'
useEffect(() => {
const timer = setTimeout(() => {
if (darkMode) {
if (head.contains(light)) {
head.removeChild(light)
}
head.appendChild(dark)
} else {
if (head.contains(dark)) {
head.removeChild(dark)
}
head.appendChild(light)
}
}, 500)
return () => clearTimeout(timer)
}, [darkMode])
This package will help you to export and use theme vars without losing performance
Using less compiler in runtime:
https://medium.com/#mzohaib.qc/ant-design-dynamic-runtime-theme-1f9a1a030ba0
Import less code into wrapper
https://github.com/less/less.js/issues/3232
.any-scope {
#import url('~antd/dist/antd.dark.less');
}

Definitive guide for styling react-tooltip components?

I am using react-tooltip, react-emotion.
I cannot figure out how to style the span in order to override default styles.
Here's what I've got so far:
import React, { PureComponent } from 'react';
import styled from 'react-emotion';
const myTooltip = (Wrapper, toolTip) => {
class TooltipWrap extends PureComponent {
render() {
return (
<span
data-tip={toolTip}
data-delay-show="250"
data-place="bottom"
className={TooltipStyle}
>
<Wrapper
{...this.props}
/>
</span>
);
}
}
return TooltipWrap;
};
export default withToolTip;
const TooltipStyle = styled.span ({
color: 'red !important';
fontSize: '48px !important';
})
Anyone have any tips or a specific definitive guide on how to style this span so I can override the defaults in react-tooltip?
The documentation is pretty spotty, and there's literally no examples anywhere on the web.
I ran into a similar issue but was able to override the default styles using styled components and passing it the ReactTooltip component
import React, { PureComponent } from 'react';
import styled from 'react-emotion';
import ReactTooltip from 'react-tooltip';
const myTooltip = (Wrapper, toolTip) => {
class TooltipWrap extends PureComponent {
render() {
return (
<span
data-tip={toolTip}
data-delay-show="250"
data-place="bottom"
>
// Replace ReactTooltip component with styled one
<ReactTooltipStyled type="dark" />
<Wrapper
{...this.props}
/>
</span>
);
}
}
return TooltipWrap;
};
export default withToolTip;
export const ReactTooltipStyled = styled(ReactTooltip)`
&.place-bottom {
color: red;
font-size: 48px;
}
`;
Using this method all you would need to do is import the newly styled component into your React file and replace the original ReactTooltip with the ReactTooltipStyled component.

Resources