How to control functions in certain media queries in React using hooks - reactjs

I am working on a React project in that I have a button, for that button I have written one onClick function now what I need is when I click the button it only needs to change background color only to mobile screen from min(0px) to max(576px) in this screen only the function change has to apply.
This is my code
import React, { useState } from 'react';
import './App.css';
function App() {
const [color,setColor]=useState('red');
const [textColor,setTextColor]=useState('white');
const changeBackGround =() =>{
{setColor("black");setTextColor('red')}
}
return (
<div className="App">
<button style={{background:color,color:textColor}} onClick={changeBackGround} className='btn btn-primary'>Click here</button>
</div>
);
}
export default App
If you have any questions please let me know. Thank you

Have a state object that updates the the className on the button click. Update the className in the css media query.
import React, { useState } from 'react';
import './App.css';
function App() {
const [color,setColor]=useState('red');
const [textColor,setTextColor]=useState('white');
const [buttonClassName, setButtonClassName] = useState("");
const changeBackGround = () =>{
setColor("black");
setTextColor('red');
setButtonClassName("btn-update");
}
return (
<div className="App">
<button
style={{background:color,color:textColor}}
onClick={changeBackGround}
className={`btn btn-primary ${buttonClassName}`}>
Click here
</button>
</div>
);
}
export default App
#media screen and (max-width: 576px) {
.btn-update {
background-color: "green";
}
}

You can do this in 2 ways
check your window.innerWidth . But this will not work when you resize your window in the browser. To Test this what you can do is resize your browser window so the width is less than 576px and refresh your screen and click the button now .
const changeBackGround =() =>{
if(window.innerWidth < 576){
setColor("black");
setTextColor('red')}
} else {
...do something
}
}
Attach an event listener which listens for your resize event , now when you resize the window the width is maintained in state.
function App() {
const [deviceSize, changeDeviceSize] = useState(window.innerWidth);
const [color, setColor] = useState('red');
const [textColor, setTextColor] = useState('white');
useEffect(() => {
const handleResize = () => changeDeviceSize(window.innerWidth);
window.addEventListener('resize', handleResize);
// don't forget to remove the event listener on unmounting the component
return () => window.removeEventListener('resize', handleResize);
}, []);
const changeBackGround = () => {
if (deviceSize < 576) {
{
setColor('black');
setTextColor('red');
}
}
};
return (
<div className="App">
<button
style={{background: color, color: textColor}}
onClick={changeBackGround}
className="btn btn-primary"
>
Click here
</button>
</div>
);
}

If you want to trigger things dynamically, use custom hooks to get window size (generic) and another custom hook to check if it's valid for mobile (can be kept in a separate hooks folder).
useWindowSize.js
// Hook from https://usehooks.com/useWindowSize/
function useWindowSize() {
// Initialize state with undefined width/height so server and client renders match
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined
});
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
}
// Add event listener
window.addEventListener("resize", handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;
}
useIsMobile.js
const MAX_SIZE_FOR = { mobile: 576 };
const useIsMobile = () => {
const { width } = useWindowSize();
return width < MAX_SIZE_FOR;
};
yourComponent.js
import React, { useState } from "react";
import { useIsMobile } from './useIsMobile'
import "./App.css";
function App() {
const [style, setStyle] = useState({ background: "red", textColor: "white" });
const isMobile = useIsMobile();
const changeBackGround = () => {
if (isMobile) {
setStyle({ ...style, background: "black", textColor: "red" });
}
};
return (
<div className="App">
<button style={style} onClick={changeBackGround} className="btn btn-primary">
Click here
</button>
</div>
);
}
export default App;
You can even change the color on the fly via useEffect
const { width } = useWindowSize();
useEffect(changeBackGround, [width]);

You can create a state variable to update when the screen gets to a certain width. In a useEffect(), you can add a eventListener to the window that listens to the screen resizing. When the screen gets resized to a certain width, we update the state and use it to do conditional rendering in the return.
const [show, setShow] = useState(false); // state value for showing / hiding
useEffect(() => {
const handleResize = () => {
window.innerWidth < 576 ? setShow(true) : setShow(false); // set hide / show
}
window.addEventListener("resize", handleResize); // add event listener
}, []);
return ({
show ? <h1>show</h1>: <h1>hide</h1>
});

Related

window.scrollTo does not work when react modal is opened

import React, { useEffect } from 'react';
import ReactModal from 'react-modal';
const Modal = () => {
useEffect(() => {
window.scrollTo(0, 0);
}, [])
return (
<ReactModal
>
{children}
</ReactModal>
)
}
I want to scroll to top when react modal is opened. For this I put "window.scrollTo(0, 0)" into useEffect. But when react modal is opened it doesn't work. Why doesn't it work properly? Here is an example:
Try alternative solution with ref:
const Modal = () => {
const divRef = useRef(null);
useEffect(() => {
divRef.current.scrollIntoView({ behavior: "auto" }); // or "smooth" behavior
}, []);
return (
<ReactModal>
<div ref={divRef}>{children}</div> // put the divRef to the place/div you want
</ReactModal>
)
}

why useRef current value , isn't sharing trough custom hook?

I wanted to calculate the user scroll height , so I created a custom hook. and I wanted to share this value to another component. but it doesnt work.
code:
const useScroll = () => {
let scrollHeight = useRef(0);
const scroll = () => {
scrollHeight.current =
window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body)
.scrollTop;
};
useEffect(() => {
window.addEventListener("scroll", scroll);
return () => {
window.removeEventListener("scroll", () => {});
};
}, []);
return scrollHeight.current;
};
export default useScroll;
the value is not updating here.
but if I use useState here , it works. but that causes tremendous amount of component re-rendering. can you have any idea , how its happening?
Since the hook won't rerender you will only get the return value once. What you can do, is to create a useRef-const in the useScroll hook. The useScroll hook returns the reference of the useRef-const when the hook gets mounted. Because it's a reference you can write the changes in the useScroll hook to the useRef-const and read it's newest value in a component which implemented the hook. To reduce multiple event listeners you should implement the hook once in the parent component and pass the useRef-const reference to the child components. I made an example for you.
The hook:
import { useCallback, useEffect, useRef } from "react";
export const useScroll = () => {
const userScrollHeight = useRef(0);
const scroll = useCallback(() => {
userScrollHeight.current =
window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body)
.scrollTop;
}, []);
useEffect(() => {
window.addEventListener("scroll", scroll);
return () => {
window.removeEventListener("scroll", scroll);
};
}, []);
return userScrollHeight;
};
The parent component:
import { SomeChild, SomeOtherChild } from "./SomeChildren";
import { useScroll } from "./ScrollHook";
const App = () => {
const userScrollHeight = useScroll();
return (
<div>
<SomeChild userScrollHeight={userScrollHeight} />
<SomeOtherChild userScrollHeight={userScrollHeight} />
</div>
);
};
export default App;
The child components:
export const SomeChild = ({ userScrollHeight }) => {
const someButtonClickHandlerWhichPrintsUserScrollHeight = () => {
console.log("userScrollHeight from SomeChild", userScrollHeight.current);
};
return (
<div style={{
width: "100vw",
height: "100vh",
backgroundColor: "aqua"
}}>
<h1>SomeChild 1</h1>
<button onClick={() => someButtonClickHandlerWhichPrintsUserScrollHeight()}>Console.log userScrollHeight</button>
</div>
);
};
export const SomeOtherChild = ({ userScrollHeight }) => {
const someButtonClickHandlerWhichPrintsUserScrollHeight = () => {
console.log("userScrollHeight from SomeOtherChild", userScrollHeight.current);
};
return (
<div style={{
width: "100vw",
height: "100vh",
backgroundColor: "orange"
}}>
<h1>SomeOtherChild 1</h1>
<button onClick={() => someButtonClickHandlerWhichPrintsUserScrollHeight()}>Console.log userScrollHeight</button>
</div>
);
};
import { useRef } from 'react';
import throttle from 'lodash.throttle';
/**
* Hook to return the throttled function
* #param fn function to throttl
* #param delay throttl delay
*/
const useThrottle = (fn, delay = 500) => {
// https://stackoverflow.com/a/64856090/11667949
const throttledFn = useRef(throttle(fn, delay)).current;
return throttledFn;
};
export default useThrottle;
then, in your custom hook:
const scroll = () => {
scrollHeight.current =
window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body)
.scrollTop;
};
const throttledScroll = useThrottle(scroll)
Also, I like to point out that you are not clearing your effect. You should be:
useEffect(() => {
window.addEventListener("scroll", throttledScroll);
return () => {
window.removeEventListener("scroll", throttledScroll); // remove Listener
};
}, [throttledScroll]); // this will never change, but it is good to add it here. (We've also cleaned up effect)

How to Avoid Memory Leak on Screen Size Change in ReactJS

I keep getting memory leak errors when switching between desktop and mobile view in google inspector (though not if I shrink/expand the screen by stretching). The error message is:
I'm not using any subscriptions or asynchronous tasks I'm aware of. The only thing I can think of that might contribute to this is that at the time of the screen switching, my AlertDialog component isn't showing. That's because it's only conditionally rendered:
return (
<Screen>
{showDialog === true ?
<AlertDialog /> :
<MainApp />
}
</Screen>
);
The error doesn't show up if the AlertDialog is on screen at the time of the change.
The code for my dialog is:
import { useEffect, useState } from "react";
import { useWindowSize } from "src/hooks";
import Dialog from '#mui/material/Dialog';
import DialogActions from '#mui/material/DialogActions';
import DialogContent from '#mui/material/DialogContent';
import DialogContentText from '#mui/material/DialogContentText';
import DialogTitle from '#mui/material/DialogTitle';
const AlertDialog = (props : any) => {
const {innerWidth, innerHeight} = useWindowSize();
const [openDialog, setOpenDialog] = useState(true);
const [buttonStyle, setButtonStyle] = useState({});
useEffect( () => { // Needs b/c isn't working right in Styled
setButtonStyle({
width: Math.max(innerWidth/10, 100),
});
}, [innerWidth, innerHeight])
const handleClick = (choseTrue : boolean) => {
setOpenDialog(false);
props.onClick(choseTrue);
}
return (
<Dialog
open={openDialog}
onClose={() => null} // Use null to prevent it from closing until an option is chosen
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title" style={{textAlign: "center"}}>{"Set Device"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description"> </DialogContentText>
</DialogContent>
<DialogActions>
<button onClick={() => handleClick(true)} style={buttonStyle}>{props.choiceTrue}</button>
<button onClick={() => handleClick(false)} style={buttonStyle}>{props.choiceFalse}</button>
</DialogActions>
</Dialog>
);
};
export default AlertDialog;
This is my screen resize code. I'm pretty sure it's the source of the issue, since if I comment out the useWindowSize line in AlertDialog, there is no error:
import { useState, useEffect } from "react";
export const useWindowSize = () => {
const [size, setSize] = useState<{
innerWidth: number;
innerHeight: number;
}>({ ...window });
useEffect(() => {
const cb = (u: UIEvent) => {
const w = u.target as Window;
setSize({ ...w });
};
window.addEventListener("resize", cb, true);
() => window.removeEventListener("resize", cb);
}, []);
return size;
};
Why am I getting this memory leak error, and how can I avoid it?
you need to return the callback function from useEffect for that cleanup function
if you want to have useScreenSizeChecker Hook I have written this hook before here is the code
import { useState, useEffect, useCallback } from "react";
/** Default size is 768px */
export const useScreenSizeChecker = (width = 768):boolean => {
const [sizeCheck, setSizeCheck] = useState(window.innerWidth < width);
const checkSize = useCallback(() => {
setSizeCheck(window.innerWidth < width);
}, [width]);
useEffect(() => {
window.addEventListener("resize", checkSize);
checkSize();
return () => {
window.removeEventListener("resize", checkSize);
};
}, [checkSize]);
return sizeCheck;
};

Add blur effect to navigation scroll bar

I am trying to implement a navbar that has a blur effect when scrolling.
This works, but when I refresh the page, the scrollbar stays in the same position and I don't get any result from window.pageYOffset. The result of this is that I have a transparent navigation bar.
I'm also using TailwindCSS, but I think this doesn't matter.
Code example:
import React, { useState, useEffect } from 'react'
const Navigation: React.FC = () => {
const [top, setTop] = useState(true);
useEffect(() => {
const scrollHandler = () => {
window.pageYOffset > 20 ? setTop(false) : setTop(true)
};
window.addEventListener('scroll', scrollHandler);
return () => {
window.removeEventListener('scroll', scrollHandler);
}
}, [top]);
return (
<header className={`fixed w-full z-30 ${!top && 'bg-white dark:bg-black bg-opacity-80 dark:bg-opacity-80 backdrop-blur dark:backdrop-blur'}`}>
</header>
);
};
export default Navigation
You need to explicitly call scrollHandler() inside the useEffect if you want the navbar to keep its blurred state when the page is refreshed.
useEffect(() => {
const scrollHandler = () => {
setTop(window.pageYOffset <= 20)
};
window.addEventListener('scroll', scrollHandler);
// Explicit call so that the navbar gets blurred when component mounts
scrollHandler();
return () => {
window.removeEventListener('scroll', scrollHandler);
}
}, []);
You can also remove top from the useEffect's dependencies array, you only need it to run when the component is mounted.

How to make react hook persists with ref

I want to toggle an input element by using custom hook.
Here's my custom hook:
import { RefObject, useEffect } from "react";
export const useEscape = (
ref: RefObject<HTMLElement>,
triggerFn: () => void
) => {
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
triggerFn();
}
};
document.addEventListener("click", handleClickOutside);
return () => window.removeEventListener("click", handleClickOutside);
});
};
and the example that would use the hook
import * as React from "react";
import "./styles.css";
import { useEscape } from "./useEscape";
export default function App() {
const [showInput, setShowInput] = React.useState(false);
const inputRef = React.useRef(null);
useEscape(inputRef, () => {
if (showInput) setShowInput(false);
});
return (
<div>
{showInput && (
<input ref={inputRef} placeholder="click outside to toggle" />
)}
{!showInput && (
<span
style={{ border: "1px solid black" }}
onClick={() => {
console.log("toggle to trigger");
setShowInput(true);
}}
>
click to toggle input
</span>
)}
</div>
);
}
Here's the link to codesandbox demo.
Here's the issue. After I clicked on the span element to toggle into input state. After click outside of the input element, it would never able to toggle back to input state again.
I guess I know why's that happening. The react ref is still pointing to the input element that was created at the first place. Howeve, when react toggle to showing span state, it unmount the input element, and my custom hook never sync with React for the new input element. How can I customize my useEscape hook so the react ref would sync up? (By the way, I want to not use styling as a workaround which visually 'hides' the input element).
import { RefObject, useEffect } from "react";
export const useEscape = (
ref: RefObject<HTMLElement>,
triggerFn: () => void
) => {
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
triggerFn();
}
};
document.addEventListener("click", handleClickOutside);
return () => document.removeEventListener("click", handleClickOutside);
}, [ref, triggerFn]);
};
Your entire logic is absolutely correct. There is a slight error, instead of
window.removeEventListener, change it to document.removeEventListener.
You are removing event listener on global window object which leads to bug.

Resources