Can you set Mouse hover to identify different elements in React? - javascript

I wanted to adapt this code show that, for example if you hovered over a specific , then the relating would also show. useState seems to be the only way to make this work in React as I tried a different example with eventlistner which crashed the page.
const Showstuff = () => {
const [isHovering, setIsHovering] = useState(false);
const handleMouseOver = () => {
setIsHovering(true);
};
const handleMouseOut = () => {
setIsHovering(false);
};
return(
<div>
<div onMouseOver={handleMouseOver} onMouseOut={handleMouseOut}>
Hover over div #1 here
</div><br /><br />
<div>
Hover over div #2 here
</div>
{isHovering && (
<div>
<h2>Text here visible when hovering div 1</h2>
</div>
)}
</div>
)
};
export default Showstuff;
I made multiple useStates for each items as a work around, but this means there's 3x const lines for each item I want to add, and I have 6 elements to hover. Can this be combined into a shorter code? I also tried:
const el = document.getElementById('container');
const hiddenDiv = document.getElementById('hidden-div');
el.addEventListener('mouseover', function handleMouseOver() {
hiddenDiv.style.visibility = 'visible';
});
el.addEventListener('mouseout', function handleMouseOut() {
hiddenDiv.style.visibility = 'hidden';
});
from a guide on bobbyhadz website but this would require the same idea of making multiple lines of the same code with different names. This works immediately after saving the page in vscode but then shortly afterwards crashes the page, and does not work - I assume it is not React compatible.

I would do something like this :
function App() {
const [isHovered, setIsHovered] = useState(null)
const handleMouseOver = (e) => {
switch (e.target.id) {
case "1":
setIsHovered(1)
break
case "2":
setIsHovered(2)
break
}
}
return (
<div className="App">
<div id="1" onMouseOver={handleMouseOver} onMouseOut={() => setIsHovered(null)}>
DIV 1
</div>
<div id="2" onMouseOver={handleMouseOver} onMouseOut={() => setIsHovered(null)}>
DIV 2
</div>
{isHovered && <h2>{isHovered === 1 ? "Div 1 is hovered" : "Div 2 is hovered"}</h2>}
</div>
)
}
That way you only use one useState hook and set the value of isHovered depending on the targetted div's id.

Instead of having isHovering be a boolean, make it something else. If your design means you can only hover one thing at a time, the simplest solution is to make isHovering just hold some ID. But if you have overlapping elements where it's possible to hover multiple at once, you can use an array of IDs, or an object where each key is an ID and each value is a boolean.
You need to modify your onMouseOver (and, possibly, onMouseOut) function(s) to pass an ID as an argument.
Here is a simple example:
const Showstuff = () => {
const [isHovering, setIsHovering] = useState();
const handleMouseOver = (id) => setIsHovering(id);
const handleMouseOut = () => setIsHovering();
return (
<div>
{[1, 2, 3, 4, 5, 6].map((n) => (
<>
<div
onMouseOver={() => handleMouseOver(n)}
onMouseOut={handleMouseOut}
>
{`Hover over div #${n} here`}
</div>
</>
))}
{isHovering && (
<div>
<h2>{`Text here visible when hovering div ${isHovering}`}</h2>
</div>
)}
</div>
);
};
You don't have to use a map function if that won't work for you. That's just what I'm doing in this example. Just make sure your IDs are unique.
If you need to be able to hover multiple items at once, you'll have to modify the handleMouseOver and handleMouseOut functions. For example, if you wanted to store the values in an array, you can do something like this:
const handleMouseOver = (id) =>
setIsHovering((oldIsHovering) => [...oldIsHovering, id]);
const handleMouseOut = (id) =>
setIsHovering((oldIsHovering) => oldIsHovering.filter((n) => n !== id));

You can use an array as a state variable and map over it:
export default function App() {
const [isHovering, setIsHovering] = useState(new Array(4).fill(false));
function handleMouseEnter(i) {
setIsHovering((prev) => {
const next = [...prev];
next[i] = true;
return next;
});
}
function handleMouseLeave(i) {
setIsHovering((prev) => {
const next = [...prev];
next[i] = false;
return next;
});
}
return (
<>
{isHovering.map((_, i) => (
<span
onMouseEnter={() => handleMouseEnter(i)}
onMouseLeave={() => handleMouseLeave(i)}
></span>
))}
{isHovering.map((v, i) => (
<p>
Hovering on {i}: {v.toString()}
</p>
))}
</>
);
}
https://stackblitz.com/edit/react-ts-owprrr?file=App.tsx
This is good if the HTML elements are the same. If all your elements are unique, you are better off using multiple states and naming them uniquely. You'll just end up making the design more confusing by trying to save a few lines of code.

Related

React tab-like system using CSS classes

I have 5 div's and 5 buttons. On each button clicked one div become visible. the other four gets hidden. I just want to ask is there any other better way to do it. Give suggestion as much as possible. Thank you!
let id1 = React.createRef()
let id2 = React.createRef()
let id3 = React.createRef()
let id4 = React.createRef()
let id5 = React.createRef()
function iid1() {
id1.current.classList.remove('hidden')
id1.current.classList.add('contents')
id2.current.classList.add('hidden')
id3.current.classList.add('hidden')
id4.current.classList.add('hidden')
id5.current.classList.add('hidden')
}
function iid2() {
id1.current.classList.add('hidden')
id2.current.classList.remove('hidden')
id2.current.classList.add('contents')
id3.current.classList.add('hidden')
id4.current.classList.add('hidden')
id5.current.classList.add('hidden')
}
function iid3() {
id1.current.classList.add('hidden')
id2.current.classList.add('hidden')
id3.current.classList.remove('hidden')
id3.current.classList.add('contents')
id4.current.classList.add('hidden')
id5.current.classList.add('hidden')
}
function iid4() {
id1.current.classList.add('hidden')
id2.current.classList.add('hidden')
id3.current.classList.add('hidden')
id4.current.classList.remove('hidden')
id4.current.classList.add('contents')
id5.current.classList.add('hidden')
}
function iid5() {
id1.current.classList.add('hidden')
id2.current.classList.add('hidden')
id3.current.classList.add('hidden')
id4.current.classList.add('hidden')
id5.current.classList.remove('hidden')
id5.current.classList.add('contents')
}
I just want the above code to be more efficient & readable. I'm looking for best practices for javascript. You can also tell me you would you solve this problem. I'm not looking for answer's. I'm here to seek best practices,
Thank you.
Use state to identify which div is the selected one. Buttons will change the state and your app will re-render adjusting the classNames for the divs.
const App = () => {
const [selected,setSelected] = React.useState(0);
const DIV_IDS = [0,1,2,3,4,5];
const selectItems = DIV_IDS.map((item) => {
return(
<button onClick={() => setSelected(item)}>{item}</button>
);
});
const divItems = DIV_IDS.map((item) => {
return (
<div key={item} className={selected === item ? 'visible' : 'hidden'}>
I am div {item}
</div>
);
});
return(
<div>
<div>{selectItems}</div>
<div>{divItems}</div>
</div>
);
};
ReactDOM.render(<App/>, document.getElementById('root'));
.hidden {
visibility: hidden;
}
.visible {
visibility: visible;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
May be best to just have the class in your JSX element classes. Something like:
<element className={(condition_for_shown) ? 'contents' : 'hidden'}>
...
</element>
and then for each button would be:
<button type="button" onClick={() => setStateConditonToSomething}>
...
</button>
Note that you'll need to store the condition in react state with useState or however you wanna store it.
The way i'd do it is -
const DivHidingComponent = ({ elementCount = 5 }) => { // element count defaults to 5
const [visibilityIndex, setVisibilityIndex] = useState(0);
const onClickCallback = useCallback((index) => () => {
setVisibilityIndex(index);
})
const buttonGroup = useMemo(() => {
const buttonGroup = [];
for (let i = 0; i < elementCount; i++) {
buttonGroup.push(
<button key={`${i}-button`} onClick={onClickCallback(i)} />
)
}
return buttonGroup;
}, [elementCount])
// only re-runs on a button click
const divGroup = useMemo(() => {
const divGroup = [];
for (let i = 0; i < elementCount; i++) {
divGroup.push(
<div key={`${i}-div`} style={{ visibility: visibilityIndex === i ? 'visible' : 'hidden' }} />
);
}
return divGroup;
}, [visibilityIndex]);
return (
<div>
<div>
{buttonGroup}
</div>
<div>
{divGroup}
</div>
</div>
);
}
I set the style directly in the div group loop, but you could assign a class name or go about setting the style however you want.
Div's visibility is set by the visibility index that is driven by the buttons being clicked on.
I passed the elementCount variable in the props so you could scale this to however many elements you want. 5 or a 1000. I assigned elementCount a value of 5 that will act as a default for when no value is passed when the component is initialized.
Also, you could drop the useMemo and useCallback hooks and it would still execute fine. But it would help improve performance if you say, set the element count to 10,000. With those hooks in place it'd only re-build the div group on re-render. That'd be the difference between running the loops 20k times (10k for buttons, 10k for divs).
I added the last paragraph incase you were not aware of React Hooks!
I hope this helps!

How to toggle a specific div in a mapped array in React?

I have a project where I'm mapping out an array with a like-button within the mapped out div.
This button should work only for the specific parent div - at the moment it toggles everything.
In my code, I tried to do it the long way around with a switch (it did not work, and also not good). I tried to look this up in various ways.
I think I need to work with e.target, but I'm not sure how to translate e.target.value to a state. For example - if I push button with value 5 (in div 5) I want the showDiv-state to take in the value (showDiv{5}) and be set to true.
What am I missing here?
import { useState} from "react";
import { BulletinBoard } from "./BulletinBoard";
import PostLiked from "./PostLiked";
const ReactPost = () => {
const [showDiv, setShowDiv] = useState(false);
const [showDiv2, setShowDiv2] = useState(false);
const [showDiv3, setShowDiv3] = useState(false);
const [showDiv4, setShowDiv4] = useState(false);
const [showDiv5, setShowDiv5] = useState(false);
const [likeButton, setLikeButton] = useState(true);
const pickAndShow= (e) => {
e.preventDefault();
const chosenItem = e.currentTarget.value;
switch(parseInt(chosenItem)){
case 1:
console.log("Ett")
setShowDiv1(true);
setLikeButton(false);
case 2:
console.log("Två")
setShowDiv2(true);
setLikeButton(false);
break;
case 3:
console.log("Tre")
setShowDiv3(true);
setLikeButton(false);
break;
case 4:
console.log("Fyra")
setShowDiv4(true);
setLikeButton(false);
break;
case 5:
console.log("Fem")
setShowDiv5(true);
setLikeButton(false);
break;
default:
console.log("Error")
}
}
}
return (
<div className="grid">
{BulletinBoard.map((item) => (
<div className="grid-item"
key={item.id}>
<span className="row">
<p>Fråga #{item.id}</p>
<h2>{item.question}</h2>
</span>
<h3>{item.answer}</h3>
<div
className={`wrapper__${item.id}`}
>
{!showDiv ? (
<button
className="likebutton"
value={item.id}
onClick={pickAndShow}
> </button>
<div data-key={0 + item.id}>
{showDiv1 ?
<PostLiked/>
enter code here
:null }
</div>
</div>
</div>
))}
</div>
);
}
export default ReactPost;
EDIT: Here is a picture of what is happening now:
enter image description here
When the checkbox is clicked, every div within the mapped divs change:
enter image description here
Instead of having 5 showDiv state properties, you can have a single shownDiv property with a number value.
setShownDiv(parseInt(evt.currentTarget.value));

How to change a class based on focus and change in an input form in React

I have a situation where I want to append a class called shrink to the label text below (Display Name) when I click or type in the input box.
My code is below:
const FormInput = ({ label, ...otherProps} ) => {
let labelClassName = 'formInput-label';
const addLabelClassName = () => {
labelClassName = `${labelClassName} shrink`;
console.log(labelClassName, 'labelClassName inside')
}
console.log(labelClassName, 'labelClassName outside')
return (
<div className="group">
{label &&
<label
className={labelClassName}
>{label}</label>
}
<input onFocus={addLabelClassName } onChange={addLabelClassName } className="formInput" {...otherProps} />
</div>
)
};
My question:
Why does when I focus/ type, at first, React outputs the correct classnames for labelClassName inside as formInput-label shrink, but immediately changes it back to formInput-label at the labelClassName outside position? How would I fix this?
I have also tried to change the code to using the UseState approach like below:
const FormInput = ({ label, ...otherProps} ) => {
const [interaction, setInteraction] = useState('');
let labelClassName = 'formInput-label';
const onInteracting = () => {
setInteraction('interacting')
}
if(interaction === 'interacting') {
labelClassName = `${labelClassName} shrink`;
}
return (
<div className="group">
{label &&
<label
className={labelClassName}
>{label}</label>
}
<input onFocus={onInteracting} onChange={onInteracting} className="formInput" {...otherProps} />
</div>
)
};
And this will append the correct class shrink to labelClassName but I'm not able to take that off when I click outside of the input/form. How may I fix this?
Thank you a ton!
The second approach is a better way because with changing state you will trigger component rerendering (the first approach will never re-render component).
In the second approach, you can take advantage of onBlur event and create a handler which will set the state to the default value. Something like this. You don't need onChange to setIntercation
...
const handleBlur = () => {
setInteraction("");
};
...
and then in input, you have to set up onBlur prop. onChange should not do the same thing as onFocus already does.
....
<input
onFocus={onInteracting}
onBlur={handleBlur}
className="formInput"
{...otherProps}
/>
....

Using variables to set attribute values in a tag

I am trying to use the vis variable in the display attribute as shown in the code. There are no errors in the console but the paragraph wont hide on pressing the button.
let vis = false;
const handleClick = () => {
vis = !vis;
render();
};
const render = () => {
const jsx = (
<div>
<h1>Toggle</h1>
<p display = {vis ? "block" : "none"} >Hi</p>
<button onClick = {handleClick}>{vis ? 'Hide details' : 'Show details'}</button>
</div>
);
ReactDOM.render(jsx, document.getElementById("app"));
}
render();
Here's a simple and working alternative:
let vis = false;
const handleClick = () => {
vis = !vis;
render();
};
const render = () => {
const jsx = (
<div>
<h1>Toggle</h1>
{vis ? <p>Hi</p> : ''}
<button onClick = {handleClick}>{vis ? 'Hide details' : 'Show details'}</button>
</div>
);
ReactDOM.render(jsx, document.getElementById("app"));
}
render();
The problem is I am not asking for other ways to do it. I just want to understand if I can use vis in display like I did in my first code snippet.
the paragraph wont hide on pressing the button.
It's the display property on the paragraph. This is a CSS property, not a stand-alone element attribute. That means it should go inside the style attribute, like this:
<p style={{display: vis ? "block" : "none"}} >Hi</p>
After making that one change, your code worked for me. Here's a working fiddle to demonstrate.
Is this what your are trying to achieve?
import React, { useState } from "react";
export default function App() {
const [toggle, setToggle] = useState(false);
return (
<div>
<button onClick={() => setToggle(!toggle)}>Toggle</button>
{toggle && <h1>Hello StackBlitz!</h1>}
<p>Start editing to see some magic happen :)</p>
</div>
);
}

How to fire function when the variables are set in a useEffect?

i have a function calculateDistance which calculate when the child component is in the center of the parent component. I want to fire the function on the onScroll event. But the variables I need for it a set in a useEffect and cannot use outside that scope. Has anyone an idea how to fix this?
export function Portfolio() {
const portfolioRef = React.useRef(null)
React.useEffect(() => {
portfolioRef.current.scrollTop = 100
}, []
)
return (
<div className={cx(styles.component, styles.scrollWrapper)}>
<div className={styles.topIcon} dangerouslySetInnerHTML={{ __html: arrow }} />
<div ref={portfolioRef} onScroll={calculateDistance} className={styles.scroll}>
<PortfolioItem
{...{ portfolioRef }}
title='Article about Kaliber Academie'
text='I wrote an article about my experience at Kaliber'
link='https://medium.com/kaliberinteractive/hoe-technologie-het-hart-van-een-luie-scholier-veranderde-3cd3795c6e33'
linkTekst='See Article' />
<PortfolioItem
{...{ portfolioRef }}
title='Article about Kaliber Academie'
text='hola'
link='#'
linkTekst='#' />
<PortfolioItem
{...{ portfolioRef }}
title='Article about Kaliber Academie'
text='hola'
link='#'
linkTekst='#' />
<PortfolioItem
{...{ portfolioRef }}
title='Article about Kaliber Academie'
text='hola'
link='#'
linkTekst='#' />
</div>
<div className={styles.bottomIcon} dangerouslySetInnerHTML={{ __html: arrow }} />
</div>
)
}
export function PortfolioItem({ text, title, link, linkTekst, portfolioRef }) {
const portfolioItemRef = React.useRef(null)
React.useEffect(() => {
const element = portfolioItemRef.current
const parent = portfolioRef.current
}, [portfolioRef])
return (
<div ref={portfolioItemRef} className={styles.componentItem}>
<div className={styles.title}>{title}</div>
<div className={styles.content}>
<div className={styles.text}>{text}</div>
<div className={styles.links}>
<a className={styles.linkTekst} href={link}>{linkTekst} </a>
<div className={styles.linkIcon} dangerouslySetInnerHTML={{ __html:arrow }} />
</div>
</div>
</div>
)
}
function calculateDistance(parent, element) {
if (!parent || !element) return 0
const parentRect = parent.getBoundingClientRect()
const parentCenter = (parentRect.top + parentRect.bottom) / 2
const elementRect = element.getBoundingClientRect()
const elementCenter = (elementRect.top + elementRect.bottom) / 2
const distance = Math.abs(parentCenter - elementCenter)
return clamp(distance / (parentRect.height / 2), 0, 1)
}
It doesnt look as if your calculated distance is being stored anywhere. You probably want the calulateDistance function to update a state variable which you refer to in Portfolio's useEffect.
You can create new variables from your component props and use useState(). you can then update them and reference them in your jsx and changing them will trigger your useEffect(), as long as you have it bounded correctly. Using props directly will only trigger your unbounded useEffect on the initial load. Additionally the reference you made 'portfolioItemRef' is only set when rendered and using it as the bound for the useEffect will not update to the calculated distance as is. move the calculated distance function into the portfolio component.
For Example:
const [stateText, setStateText] = useState(text)
useEffect(()=>{console.log('do something')},[stateText]);
here is a helpful explanation: https://medium.com/better-programming/tips-for-using-reacts-useeffect-effectively-dfe6ae951421
Best thing you can do is listen for the scroll event inside the useEffect
remove the onScroll attached to element
....
<div ref={portfolioRef} className={styles.scroll}>
....
inside useEffect
React.useEffect(() => {
const calculateDistance = (parent, element) => {
if (!parent || !element) return 0
const parentRect = parent.getBoundingClientRect()
const parentCenter = (parentRect.top + parentRect.bottom) / 2
const elementRect = element.getBoundingClientRect()
const elementCenter = (elementRect.top + elementRect.bottom) / 2
const distance = Math.abs(parentCenter - elementCenter)
return clamp(distance / (parentRect.height / 2), 0, 1)
}
//attach event listener
portfolioRef.current.addEventListener("scroll", calculateDistance);
return () => {
// remove the event listener when component unmounts
portfolioRef.current.removeEventListener("scroll", calculateDistance);
}
}, [])
Demo
you have to decide how to get the parent and element inside the calculateDistance

Categories

Resources