onClick event on element on Mobile device hover state is still applied - javascript

I can't seem to find anyone with this same issue online with React, unless I've been searching for the wrong thing?
I have a button (anchor tag) that has an onClick event. When the onClick event is triggered it calls a loadMore() function that makes an API call and updates some state.
However on a mobile device (not mobile resolution on desktop!) when I click the button, the onClick event works and returns what is expected, however it applies a hover state on the button. For the button hover state I have a background color applied
The hover state will stick until I click away anywhere else on the screen. So the background color sticks until I click away.
Now, this isn't exactly desirable. Why is this happening, and how did I prevent it?
Here is my Button Component
const Button = props => {
const buttonDisabledClass = props.disabled ? "Button--disabled " : "";
const hiddenClass = props.hidden ? "Button--hidden " : "";
const modifierClass = props.modifier ? props.modifier : "";
return (
<>
<a
onClick={!props.disabled ? props.onClick : undefined}
className={
"Button " + buttonDisabledClass + hiddenClass + modifierClass
}
>
{props.children}
{props.buttonText ? (
<span
className={`Button-text ${
props.buttonMobileText ? "Button-desktopText" : ""
}`}
>
{props.buttonText}
</span>
) : null}
{props.buttonMobileText ? (
<span className="Button-mobileText">{props.buttonMobileText}</span>
) : null}
</a>
</>
);
};
Here is the parent component
The parent component imports the Button component and has the function that makes the API request (just have a simulated one here as an example)
function App() {
const [number, setNumber] = useState(0);
/*simulate http request*/
const ttl = 500;
const loadMore = () => {
const timeout = setTimeout(() => {
setNumber(number + 1);
}, ttl);
return () => {
clearTimeout(timeout);
};
};
return (
<div className="App">
{number}
<Button
key={"loadMoreBtn"}
modifier="Button--loadMore Button--Inline"
onClick={() => loadMore()}
>
Load More
</Button>
</div>
);
}
So, how can I make it so a click on a mobile device does not register hover but still have the hover working on a desktop device as it currently is?
I have a CODESANDBOX if you wish to test it out for yourself
Here is a link for you to test on your mobile device.
The button is orange by default, and grey on hover. On mobile, this is what happens when you click...
Any help would be greatly appreciated!

You could override the hover effect on mobile using a media query in your CSS:
#media only screen and (min-resolution: 117dpi) and (max-resolution: 119dpi), only screen and (min-resolution: 131dpi) and (max-resolution: 133dpi), only screen and (min-resolution: 145dpi) and (max-resolution: 154dpi), only screen and (min-resolution: 162dpi) and (max-resolution: 164dpi), only screen and (min-resolution: 169dpi) {
.Button:hover {
background-color: #ee4900 !important;
}
}
Or
#media (hover: none) {
.Button:hover {
background-color: #ee4900 !important;
}
}

Related

HTML, CSS, NextJS - How can I prevent scroll freeze when a page is in desktop mode?

I have a single page application using NextJS.
I use useEffect to hide nav-menu by changing body className as below code:
const [ navbarOpen, setNavbarOpen ] = useState(false);
const ref = useRef();
useEffect(() => {
const handler = (e) => {
if (
navbarOpen &&
ref.current &&
!ref.current.contains(e.target)
) {
setNavbarOpen(false);
}
};
document.body.className = navbarOpen ? '' : 'mobile-nav-active';
}, [navbarOpen]);
and CSS property:
.mobile-nav-active {
overflow: hidden;
}
In desktop mode, when I click the menu, body className is changed to "mobile-nave-active" then I cannot scroll up or down.
How can I set the body property only in mobile mode?
Refer to page: https://mairesume.vercel.app
I think the problem is from this line of the code :
document.body.className = navbarOpen ? '' : 'mobile-nav-active';
it works in desktop and mobile the same but the way that you are treating menu is different in each one,
if you add condition for mobile checking before assigning mobile-nav-active (because you don't need it to be applied in desktop mode) your problem will be fixed
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
document.body.className = navbarOpen ? '' : 'mobile-nav-active';
}

Display overflow indicator in React

I'm working on a commercial project in React where customer wants to display used and unused devices on one screen of the website. On the left, there is one div with used devices which takes up max 70% of the screen width and on the right is div with unused devices, which takes up min 30% of the screen width (or rather 550px). The number of used devices on the right may vary, so if there is not enough to fill the 70% of the screen, unused devices on the right will expand to fill the gap (display: flex). However, if there is more used devices than can fit in the 70% of the screen width, I want to display a custom overflow indicator on the left side of unused devices. The reason for this, is that the used devices will go under the used devices (z-index 0 and 1) and I want to show that there is overflow of used devices, that there are more than user can see. For this overflow indicator I wanted to use a gradient on the left side of unused devices. I did manage to achieve this, but with some problems.
I used a state boolean variable showMore to indicate when should I display the overflow indicator. The value change of this variable is happening in custom function checkOverflow:
function checkOverflow() {
if (ref.current && refUnused.current) {
if (
window.innerWidth - refUnused.current?.clientWidth <
ref.current?.clientWidth
) {
setShowMore(true);
} else {
setShowMore(false);
}
}
}
ref is representing used devices and refUnused unused devices. I use this function in useEffect and useLayoutEffect as I need to check this on load and on window resize.
useLayoutEffect(
() => {
if (reload()) {
checkOverflow();
}
window.addEventListener("resize", checkOverflow);
return function cleanupListener() {
window.removeEventListener("resize", checkOverflow);
};
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[ref]
);
useEffect(
() => {
checkOverflow();
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[ref]
);
However, the problem I ran into is flickering in the situation where the values differ just slightly. It's flickering between showing the overflow indicator and not showing it. I also get console error message
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
Does anyone know how to prevent this flickering and React error/warning? Or any idea on how to achieve this differently?
Here is the code:
<div className="vehicle-main-panel">
<div className="vehicle-standorten" ref={ref}> //used devices
{(() => {
const array = [];
items.stos.map((item, index) => {
return array.push(
<Veh
standort={item}
vehicles={item.vehicles}
key={index}
isVehicle={true}
></Veh>
);
});
return array;
})()}
</div>
<div className="vehicle-right-panel"> //unused vehicles and gradient
<div className="vehicle-overflow-hor-1">
<div className="vehicle-card-overflow">
<div className="vehicle-card-overflow-box gradient-90">
<p style={{ fontWeight: "bold", fontSize: "40px" }}>
... {/* {t("vehicles.more-items")} */}
</p>
</div>
</div>
</div>
<div
className={
showMore
? "vehicle-nonfunctional-absolute"
: "vehicle-nonfunctional-flex"
}
ref={refUnused}
>
<Veh value={items.notReady} isVehicle={false}></Veh>
</div>
</div>
</div>

React Spring useSpring hook switching between from state and to state without animation

I've been stuck trying to get some divs to simply fade in when they become visible on the screen. Maybe I'm missing something, but here's the code.
I'm using the modules React-Spring V9 and React-Visibility-Sensor.
Parent Component's Render:
{
ArrayOfText.map(text => (
<VisibilitySensor key={text}>
{({ isVisible }) => (
<MyItem isVisible={isVisible} text={text} />
)}
</VisibilitySensor>
))
}
Child Component:
export default function MyItem({ text, isVisible }) {
const animatedStyle = useSpring({
config: { ...config.gentle },
to: {
opacity: isVisible ? 1 : 0
}
});
return (
<animated.div style={animatedStyle} className='large-header-text'>
{text}
</animated.div>
);
}
This works in that divs will appear on screen with a slight delay after they come into view. The problem I'm having is there's no animation. It's just opacity 0, then wait ~1 second, then instantly opacity: 1.
If anyone has run into this issue before please let me know how you solved it! Thank you.
I discovered the problem. I doubt this will help anyone else, but the reason it wasn't animating was because I was trying to have text fade in that had the following styles applied to it:
background: -webkit-linear-gradient(60deg, #1369BF, #B07D2E, #8FD714);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
This gives the text a gradient color, but I should have known this would prevent something like animating opacity.

Make SearchBar dissappear when resizing manipulating DOM in script

I have a header with the main navigationBar and a searchBar that appears only on small devices when clicking on an icon. For activating the searchBar I have a hook called useBoolean that returns a searchBarisShown, openSearchBar, closeSearchBar, and toggleSearchBar.
I have a wrapper for both navigationBar and searchBar and for animation purposes for the shadow I have another wrapper. It looks like this.
const Header = () => {
// Hooks
const [isSearchShown, openSearchBar, closeSearchBar, toggleSearch] = useBoolean()
const topMenuAnimation = useSpring({
transform: isSearchShown ? `translateY(0)` : `translateY(-100%)`,
})
const headerHeightAnimation = useSpring({
// 99px = 48px from search bar + 51px from header
height: isSearchShown ? '99px' : '51px',
})
if (window.innerWidth >= '990px') {
closeSearchBar()
}
return (
<HeaderMainContainer style={headerHeightAnimation}>
<HeaderBar>
{content}
</HeaderBar>
<SearchBar style={topMenuAnimation}>
<SearchFieldBar />
</SearchBar>
</HeaderMainContainer>
)
}
The thing is I want to close my searchBar with closeSearchBar when resizing over 990px because otherwise, I have my shadow way down below my navigation bar when this is closed. If I try to do this on styled-components with a media query it doesn't work because I am using styled-components and this only manipulates the virtual DOM and useSpring manipulates de real DOM. Do you have any suggestions on how to make this searchBar disappear?

How do I (correctly) check whether an element is within the viewport in React?

In a functional react component, I'm trying to check whether a call to action button (a different component) is within the viewport. If it's not, I want to display a fixed call to action button at the bottom of the viewport, which shows/hides, depending on whether the other button is visible.
I can do this using a combination of Javascript and react hooks, but although the code works in some components in my app, it doesn't work in others; I'm guessing due to react lifecycles.
I'm also aware that this is NOT the way I should be doing things in react, so would prefer to achieve the same result, but in a proper 'react way'.
I've been looking at using refs, but ideally wanted to avoid having to change my functional component to a class, as I'd like to use react hooks for the show/hide of the fixed cta. However, if this is a requirement in order to get the functionality I want, I could go for that.
Here's what I've got so far - basically, I want to replace document.querySelector with a react method:
useEffect(() => {
const CTA = document.querySelector('#CTANextSteps');
const ApplyStyle = () => (isInViewport(CTA) ? setVisible(false) : setVisible(true));
ApplyStyle();
window.addEventListener('scroll', ApplyStyle);
window.addEventListener('resize', ApplyStyle);
return function cleanup() {
window.removeEventListener('scroll', ApplyStyle);
window.removeEventListener('resize', ApplyStyle);
};
});
const isInViewport = (elem) => {
const bounding = elem.getBoundingClientRect();
return (
bounding.top >= 0 &&
bounding.left >= 0 &&
bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
As mentioned above, this function works in some areas of the app without issue, but doesn't in others; I get a Cannot read property 'getBoundingClientRect' of null error. I was surprised it worked at all, but rather than tinkering with it to try and get it working everywhere, I want to rewrite it properly.
As always, any assistance would be much appreciated. Thanks.
I was able to do it with the depedency react-visibility-sensor#5.1.1
I followed the tutorial in this link and it worked fine with me.
I don't know if this is the correct way to do it, but it works!
Here is the link https://www.digitalocean.com/community/tutorials/react-components-viewport-react-visibility-sensor
I'll put an example just in case the previous link ever goes out.
import React, { Component } from 'react';
import VisibilitySensor from 'react-visibility-sensor';
class VisibilitySensorImage extends Component {
state = {
visibility: false
}
render() {
return (
<VisibilitySensor
onChange={(isVisible) => {
this.setState({visibility: isVisible})
}}
>
<img
alt={this.props.alt}
src={this.props.src}
style={{
display: 'block',
maxWidth: '100%',
width: '100%',
height: 'auto',
opacity: this.state.visibility ? 1 : 0.25,
transition: 'opacity 500ms linear'
}}
/>
</VisibilitySensor>
);
}
}
export default VisibilitySensorImage;

Categories

Resources