Problems displaying new div when onMouseOver event fires - javascript

I've made a CSS grid displaying a <p>{name}</p> element on each grid item. When an onMouseOver hover events I want to display a new div (contained within the react fragment), but in the same position. Instead of using CSS, I've used JavaScript to code the logic for this - when a user hovers on the grid item, it flips a boolean (saved as state variable isMouseHovering) from false to true.
I have a conditional statement that displays the 'new' div, when this state value is truthy.
The problem is that this logic applies for grid item container element, and not for the elements inside the grid container. So when I over over grid, it does what it's supposed to do and displays the new div. However when I hover over the p-tag within the grid item container element, it flickers between states.
How do I structure this so that the div changes on hover, no matter where on the grid item the user hovers the mouse?
Project.js
import { useState } from 'react'
const Project = ({name,url,techStack,image,blurb}) => {
const [isMouseHovering, setMouseHovering] = useState(false);
const handleOnMouseOver = () => setMouseHovering((isMouseHovering) => !isMouseHovering);
const handleMouseOut = () => setMouseHovering(false);
return(
<div className = 'grid-item' onMouseOver={handleOnMouseOver} onMouseOut = {handleMouseOut}>
{
isMouseHovering ? (
// element I want to display when mouse hovering, in same position as <p>{name}</p> element
<>
<a href='https://xxxxxxxxxxxxxxxx'>
<p>website</p>
</a>
</>
)
:
<p>{name}</p>
}
</div>
)
}
export default Project

The issue is you're using onMouseOver and onMouseOut instead of onMouseEnter and onMouseLeave.
onMouseOut will trigger when you hover over a children of the element you are in causing the bug you're describing.
Also you're toggling the state in your handleMouseOver function but from what I understood you want it to be set to true when you hover the div.
import { useState } from 'react';
const Project = ({ name, url, techStack, image, blurb }) => {
const [isMouseHovering, setMouseHovering] = useState(false);
const handleMouseEnter = () => setMouseHovering(true);
const handleMouseLeave = () => setMouseHovering(false);
return (
<div
className="grid-item"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{isMouseHovering ? (
// element I want to display when mouse hovering, in same position as <p>{name}</p> element
<>
<a href="https://xxxxxxxxxxxxxxxx">
<p>website</p>
</a>
</>
) : (
<p>{name}</p>
)}
</div>
);
};
export default Project;
https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseenter_event

Try this:
import { useState } from 'react'
const Project = ({name,url,techStack,image,blurb}) => {
const handleOnMouseOver = (dir) => {
const ele = document.getElementById('dev_span');
if (dir === 'in' && ele) {
ele.style.display = 'block';
} else if (ele) {
ele.style.display = 'none';
}
};
return(
<div
className="grid-item"
onMouseEnter={() => handleOnMouseOver('in')}
onMouseLeave={() => handleOnMouseOver('out')}
>
<span id="dev_span" style={{ display: 'none' }}>
<a href="https://xxxxxxxxxxxxxxxx">
<p>website</p>
</a>
</span>
<p>{name}</p>
</div>
)
}

You can check for the element that is trigger the event.
import { useState } from "react";
const Project = ({ name, url, techStack, image, blurb }) => {
const [isMouseHovering, setMouseHovering] = useState(false);
const handleOnMouseOver = (e) => {
if (e.target.className === "grid-item") {
setMouseHovering((isMouseHovering) => !isMouseHovering);
}
};
const handleMouseOut = () => setMouseHovering(false);
return (
<div
className="grid-item"
onMouseOver={(e) => handleOnMouseOver(e)}
onMouseOut={handleMouseOut}
>
{isMouseHovering ? (
// element I want to display when mouse hovering, in same position as <p>{name}</p> element
<>
<a href="https://xxxxxxxxxxxxxxxx">
<p>website</p>
</a>
</>
) : (
<p>{name}</p>
)}
</div>
);
};
export default Project;

Related

How to access the state of a component at a superior level without using useContext?

let me explain my question.
I would like to create expanding flex cards, here is the exemple on codepen : https://codepen.io/z-/pen/OBPJKK
and here is my code for each button :
basically I have a component which is called HomeButtons that generates every flex cards. Inside this component I have a smaller component called readMore. In this component I have a useState that allows me to toggle individually each button to add or retreive an active class. If the active class is present, that means that the selected button must expand and the other ones must shrink.
What I would like to do is to access the readMore state ouside of the readMore subcomponent. That way I could write a function to remove the active class from a card if the user clicks on another card like so :
function setToUnactive() {
if (readMore(true)) {
readMore(false)}
}
My question is how can I get the state of readMore outside of the readMore subcomponent ? Do I need to use useContext ? Because that seems very simple to do but I tried a lot of things and nothing works. Can I pass the state readMore as a prop of the component ReadMore ? Thank you !
import React, { useState } from 'react';
import '../style/catalogue.scss';
import collectionsItems from '../Components/collectionsItemsData';
import { Link } from "react-router-dom";
const HomeButtons = ({}) => {
function ReadMore() {
const [readMore, setReadMore] = useState(false)
function toggleSetReadMore() {
setReadMore(!readMore)
}
return (
<p className='showmore' onClick={toggleSetReadMore} className={readMore ? "collection-item active" : "collection-item"}>TOGGLE BUTTON</p>
)
}
return <div>
{collectionsItems.map((collectionItem) => {
const { id, category, img } = collectionItem;
return < article key={id} >
<img className="item-img" src={img} alt=''/>
<ReadMore />
<Link to={`/${category}`} className="item-title">{category}</Link>
</article>
})}
</div>
}
export default HomeButtons;
First of all you need extract ReadMore component from function outside!
And for your problem you can lift state up(https://reactjs.org/docs/lifting-state-up.html). And since at the same time only one item can be opened you can do something like this:
function ReadMore({ isOpened, setOpened }) {
return (
<p
onClick={setOpened}
className={isOpened ? "collection-item active" : "collection-item"}
>
TOGGLE BUTTON
</p>
);
}
const HomeButtons = () => {
const [openedItemId, setOpenedItemId] = useState(null);
return (
<div>
{collectionsItems.map((collectionItem) => {
const { id, category, img } = collectionItem;
return (
<article key={id}>
<img className="item-img" src={img} alt="" />
<ReadMore
isOpened={openedItemId === id}
setOpened={() => setOpenedItemId(id)}
/>
<Link to={`/${category}`} className="item-title">
{category}
</Link>
</article>
);
})}
</div>
);
};

JavaScript element.contains returns false for conditionally rendered components

I am building a form selector component and I want the dropdown element to close when a user clicks elsewhere on the screen.
The component is designed as follows:
export const FormSelect = ({
defaultText,
disabled,
formError,
formSuccess,
formWarning,
groupTitle,
items,
isOpen,
selected,
selectorId,
selectorWidth,
setSelected,
setIsOpen,
textCaption,
textCaptionError,
textCaptionSuccess,
textLabel,
}) => {
const toggling = () => setIsOpen(!isOpen);
const mainSelector = React.useRef(null);
const closeMenu = () => {
setIsOpen(false);
};
React.useEffect(() => {
var ignoreClickOnMeElement = document.getElementById("option-container");
document.addEventListener("click", function (event) {
var isClickInsideElement = ignoreClickOnMeElement.contains(event.target);
if (!isClickInsideElement) {
closeMenu();
}
});
}, []);
return (
<div id={"option-container"}>
<SelectContainer>
{isOpen ? (
<ArrowUp />
) : (
<ArrowDownward />
)}
</SelectContainer>
<div>
{isOpen ? (
<OptionContainer>
<OptionList>
{groupTitle ? (
{items.map((item, index) => (
<ListItem/>
))}
</OptionList>
</OptionContainer>
) : null}
</div>
</div>)
The useEffect hook contains the logic to close the component if a user clicks outside of the "option-container". However, the form will not open if a user clicks directly on ArrowDown. I am fairly certain this is because the logic checking if the user click is within "option-container" is resulting in false.
if (!isClickInsideElement) {
closeMenu();
}
Why is the conditionally rendered element not considered inside the container around it?
This element:
var ignoreClickOnMeElement = document.getElementById("option-container");
is retrieved once, right after the component mounts. But there's no guarantee that that exact element is the same one that exists in the DOM when the click listener runs. If you try logging ignoreClickOnMeElement.isConnected, it may give you false, indicating that it's not in the DOM anymore.
Retrieve the element only inside the click listener instead - or, even better, use a ref instead of using getElementById.
Live demo:
const FormSelect = () => {
React.useEffect(() => {
document.addEventListener("click", function (event) {
var ignoreClickOnMeElement = document.getElementById("option-container");
var isClickInsideElement = ignoreClickOnMeElement.contains(event.target);
console.log('isClickInsideElement', isClickInsideElement);
});
}, []);
const [open, setOpen] = React.useState();
return (
<div id="option-container">
option container content
<button onClick={() => setOpen(!open)}>toggle</button>
{open ? <div>open</div> : <div>closed</div>}
</div>
);
};
ReactDOM.render(<FormSelect />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div>
other content outside option container
<div class='react'></div>
other content outside option container
</div>

Closing Active Element in React Accordion Component

I'm building out a simple accordion component for a product page in my Next.js/react app. I've got it mostly working, however when a user clicks open a new accordion item I need to close the active one. Here's what my component looks like:
import React, { useRef, useState } from 'react';
import css from 'classnames';
import s from './ProductAccordion.module.scss';
interface FeatureProps {
title: string;
copy: string;
}
export const ProductAccordion = ({ content }: any) => {
return (
<div className={s.productAccordion}>
{content.features.map((feature: FeatureProps) => {
const [active, setActive] = useState(false);
const activeClass = active ? 'active' : '';
const toggleAccordion = () => {
setActive(!active);
};
return (
<div
className={css(s.productAccordion__section, s[activeClass])}
key={feature.title}
>
<button className={s.sectionTitle} onClick={toggleAccordion}>
<p className={s.sectionTitle__title}>{feature.title}</p>
<span className={s.button} />
</button>
<div className={css(s.sectionContent, s[activeClass])}>
<div className={s.sectionContent__copy}>{feature.copy}</div>
</div>
</div>
);
})}
</div>
);
};
How can I get my active accordion item to close when a new one is clicked? Thanks!
I would suggest:
moving your useState hook a level higher
instead of "active" being a boolean, make it a string that you can use to identify which item should be active
hopefully a feature has a unique identifier like an id or something that you can use to identify
You could do something like:
export const ProductAccordion = ({ content }: any) => {
const [active, setActive] = useState(''); // set up your useState here, so its value is available to all children elements
return (
<div className={s.productAccordion}>
{content.features.map((feature: FeatureProps) => {
const isActive = active === feature.id // feature.id here is just a stand in for some unique identifier that each feature has
const activeClass = isActive ? 'active' : '';
const toggleAccordion = () => {
if (isActive) {
setActive(''); // if the current item is active, and you toggle it, close the accordian
} else {
setActive(feature.id) // if the current item is not active, and you toggle it, open this section
}
};
return (
<div>
{/* your code here */}
</div>
);
};
Of course, there are many approaches you could take, and I'm sure a more elegant one than this exists. But this should hopefully get you in the right direction!

how to hide menu slider on clicking a body using react js?

I wanted to hide menu slider onclicking a body in reactjs. how can i do that in function using react.js.
document.querySelector('.open-menu').onclick = function(){
html.classList.add('active');
};
document.querySelector('.close-menu').onclick = function(){
html.classList.remove('active');
};
html.onclick = function(event){
if (event.target === html){
html.classList.remove('active');
}
};
I want this same functionality in react js.
Check the code below.
import React, { useState } from 'react';
const SomeComponent = () => {
const [isMenuOpen, showMenu] = useState(false)
const toggleMenu = () => showMenu(!isMenuOpen)
return (
<>
{isMenuOpen && <MenuCompoment />}
<div onClick={toggleMenu}><App /></div>
</>
)
}
This is a stripped down version of code I've used before.
UseEffect on mounting of the Menu adds an event listener on the document for the click event.
When a click happens it uses closest to look up the parent tree of elements for an id (note the '#')
If it finds one, then the click happened on the menu otherwise it happened on any other element so close.
When the menu is disposed the return function of useEffect is called and removes the event handler.
import React, {useState, useEffect} from 'react';
const Page = () => {
const [toggle, setToggle] = useState(false);
return <div>
<button type="button" onClick={e => setToggle(!toggle)}>Toggle</button>
{ toggle && <Menu show={toggle} hide={() => setToggle(false)}/>}
</div>
}
const Menu = ({show, hide}) => {
useEffect(() => {
document.addEventListener("click", listen);
return () => {
document.removeEventListener("click", listen);
}
}, []);
const listen = e => {
if (!e.target.closest("#menu")) {
hide();
}
}
return <div className="menu" id="menu">
<span>I'm a menu</span>
</div>;
}
i think setting onclick event on the menuItems like this will Work
onClick={()=> setOpen(!open)}
export function SidebarMenu({open, setOpen}) {
return (
<div open={open}>
<Link to="#" title="Home" onClick={()=> setOpen(!open)} >Home</Link>
</div>
)
}
Probably too late for answer but since I saw it in active feed, I will try my best to answer it.
I can see that if your menu is open, you want to hide it if clicked anywhere else. I have used useRef to store the menu node and I compare it to the document whenever its open, if it is, I close the menu
Codesandbox link

Tooltip positioning in table cell

I'm using React and have a table with some actions (delete, edit, etc.) in the cell. And I need to put a tooltip on each action. I'm not using the jquery and don't plan to, and not title props (I will need to upgrade this tooltip to some specific data or even another component).
So the problem is I can't position the tooltip correctly (for example in the middle of the top or bottom). Witch params should my component receive and how to do it with css?
const Tooltip = ({position = 'top', display, style, children}) => {
let displayClass = display ? `fade ${position} in` : `tooltip-${position}`
return (
<div className={`tooltip ${displayClass} `} role='tooltip'>
<div className='tooltip-arrow' />
<div className='tooltip-inner'>
{children}
</div>
</div>
)
}
const ActionLinkItem = ({page, action, data, onMouseEnter, onMouseLeave, display, tooltipText, id}) => {
const {buttonClass, icon} = actionsStyles[action]
return (
<Link to={`/${page}/${action.toLowerCase()}/${data.id}`}>
<a
className={`btn btn-xs ${buttonClass}`}
id={id}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
><i className={`fa fa-${icon}`} />
<Tooltip display={display} action={action}>{tooltipText}</Tooltip>
</a>
</Link>
)
}
export default class Actions extends Component {
constructor (props) {
super(props)
this.state = {
tooltipActive: ''
}
}
handleHover (event) {
this.setState({
tooltipActive: event.target.id
})
}
handleBlur (event) {
this.setState({
tooltipActive: ''
})
}
getActionsTemplate () {
const {actions, data, page} = this.props
return actions.map(action => {
let display = this.state.tooltipActive === `${action.action}-${data.id}`
let id = `${action.action}-${data.id}`
let tooltip = tooltipText[action.action].replace(/{type}/g, page).replace(/{item}/g, data.name
return <ActionLinkItem
key={`${data.id}-${action.action}`}
page={page}
action={action.action}
data={data}
id={id}
tooltipText={tooltip}
display={display}
onMouseEnter={(e) => this.handleHover(e)}
onMouseLeave={(e) => this.handleBlur(e)}
/>
})
}
render () {
return (
<div className='row'>
{this.getActionsTemplate()}
</div>
)
}
}
At its simplest, tooltip should be absolutely positioned within a positioned element.
So, add a style of position: relative to the in ActionLinkItem
and add a style of `position: absolute to the outer in Tooltip.
For added credit, you will want to set a width on your tooltip, and position or center it within the of ActionLinkItem using styles like bottom: 100%.
You can also do some calculations to ensure that your tooltip does not run off the page by moving it left and right if the containing is on the right or left respectively.

Categories

Resources