why tooltip is not hide on mouseleave event in react? - javascript

I am trying to show tooltip on mouse enter and hide on mouse leave.first i make a simple demo which is working fine.
https://codesandbox.io/s/exciting-shannon-4zuij?file=/src/list.js
above code working fine on hover it show's the tooltip and hide on leave.
see same concept i apply on a application.(this code is not working)
https://codesandbox.io/s/cool-liskov-8rvjw?file=/src/App.js
when I hover a item is show the tooltip.but it is not hiding the tooltip when you leaving the item.something went wrong.
const Student = ({students,clickHandler}) => {
console.log(students,"---ee")
const [selectedStudent,setSelectedStudent] = useState(null)
const onMouseHandler = (student,e)=>{
student.visibility = true
setSelectedStudent(student)
}
const onMouseLeaveHandler = (student)=>{
console.log('======',student)
student.visibility = false
setSelectedStudent(student)
}
return (
<ul className="student-container">
{
students && students.length > 0 ? students.map((student,index)=>{
return (
<li key={index} onClick={()=>{
clickHandler(student)
}}
onMouseLeave={()=>{
onMouseLeaveHandler(student)
}}
onMouseEnter={(e)=>{
onMouseHandler(student,e)
}} style={{position:'relative'}}>
<a><span>{student.name}</span></a>
{student.visibility? <ToolTip showToolTip={student.visibility} selectedStudent={selectedStudent}/>:null}
</li>
)
}):null
}
</ul>
);
};
export default Student;
Step too reproduce
Hover on first item Raj
and then try to hover sameer.both tooltip will display.I want only one tooltip will be display which is hovered.
I want my handlers should be in my functional component . I don't want to move these handler to parent component and pass handler as a props

In your demo it's also not work well, - one hide only when open another.
when you set student.visibility you not set state, so nothing has rerendered.
Then when you call setSelectedStudent you pass there just the same referance as was before, since it's the same object, so the state not changed, and again - nothing got rerendered.
What you have to do is pass the updated student in a new variable. like so:
setSelectedStudent({...student})
Then all should work

Related

How to add a class of "active" to individual elements in a functional navigation component in react?

This is something quite simple but somehow resulted in a crazy rabbit hole.
This link shows what I want:
https://www.w3schools.com/howto/howto_js_active_element.asp
Nothing special, now the thing becomes hairy for me when the elements in the navbar are rendered from an array of objects (from the specs). The approach I am following is basically rendering a list of buttons, this list of buttons is the state, since supposedly when you update a state it triggers a re-render, then when a button is clicked it "sets" the active class to false on the entire array-state then activates it only for the clicked one. So far it works.
The problem is that the active class is rendered two steps behind. One for the moment when the class in the array-state's elements are set to false, the other when the clicked element gets updated.
As far as I understand useState and setState are queues, hence those are applied asynchronously on each render, in order to avoid that and get the renders to show the current state, useEffect is utilized.
Now the thing is that I am not sure how to apply useEffect in order to achieve the immediate render of the "active" class.
This is the code I have:
import { options } from 'somewhere...'
export default function SideMenu(props){
let auxArr = []
let targetName
const [stateOptions, setStateOptions] = useState([...options])
const [currentOption, SetCurrentOption] = useState({})
function activeOption(e){
// this helps with event bubbling
if (e.target.tagName == "P" || e.target.tagName == "SPAN"){
targetName = e.target.parentElement.id
} else if (e.target.tagName == "IMG"){
targetName = e.target.parentElement.parentElement.id
} else {
targetName = e.target.id
}
// since the main state is an array of objects I am updating it
// in three steps, first the current object is "activated"
// then the main array-state gets "inactivated" to erase all
// the previous "active" classes, finally the activated object
// replaces the corresponding inactive object in the main state.
let targetElement = stateOptions.filter(e => e.id==targetName)[0]
SetCurrentOption({
id: targetElement.id,
activity:true,
img: targetElement.img,
name: targetElement.name
})
// first the "classes" are set to false, then the
// "activated" object replaces the corresponding one
// in the main object, from here comes the two
// steps delay.
auxArr = [...stateOptions]
auxArr.forEach(e => e.activity=false)
setStateOptions(auxArr)
const newOptions = stateOptions.map(e =>
e.id==currentOption.id ? currentOption : e
)
setStateOptions(newOptions)
}
return(
<aside className={styles.sideDiv}>
<nav>
{stateOptions.map(({id, img, name, activity, link}) => {
return(
<button key={id} id={id} onClick={activeOption} className={activity?styles.active:""}>
<Image src={img}/>
<p className={timeColor.theme}> {name} </p>
</button>
)
})}
</nav>
</aside>
)
}
Thanks in advance for any help you can provide.

onClick doesn't work on custom created element

Greetings
I have built a search and every time user types word it renders new checkboxes but new checkboxes don't work like they used to be none of the event listeners work on new checkboxes, when I'm clicking on checkboxes they just don't react, but in old ones, until search will render this they are working normally
//search in checkbox data
const checkOptions = (container, value, containerId) => {
for (let i = 0; i < props.unique[containerId].length; i++) {
let item = props.unique[containerId][i];
if (
props.unique[containerId][i] !== null &&
props.unique[containerId][i].includes(value)
) {
element = (
<label
onClick={(e) => {e.stopPropagation(); ifAnyChecked(e);}} key={i}>
<input onClick={(e) => {tableSearch(e);}} type="checkbox" value={item ? item : "empty"}/>
{item && item.length > 28 ? (
handleCheckbox(item)
) : (
<p>{item}</p>
)}
</label>
);
tempData += ReactDOMServer.renderToString(element);
}
}
container.innerHTML = tempData;
};
any idea what's happening?
Have you tried to use onChange event instead of onClick? As far as I know, input type checkbox doesn't have such an event like onClick.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox
I used to get this problem when I was working with Vanilla JS whenever i render a new element then that element was not triggering my events. That was because they were generated on runtime so the event wasn't bound to that element. Now I think that thing is happening here as well. So I changed your code and put it inside a state now it is working. I hope I helped. Do let me know if this is not the solution that you were looking for but it solves your problem though
I put the html inside a state array then i mapped it out inside the newCheckBox div. I changed the input to controlled input with fieldValue state. Lastly i changed the new checkbox alert from onClick={alert("doesn't goes in")} to onClick={() => alert("I think its working now right?")}
Here is the complete code sandbox
https://codesandbox.io/s/polished-sea-vedvh?file=/src/App.js

Targeting only the clicked item on a mapped component ( React quiz trivia App)

i'm trying to develop an App with React using the Open trivia Api. I have mapped a button component (using material ui) to show the different answers for each question. I'm struggling now to target only the clicked one to apply a css property: if the answer is correct should become green, else red. The problem is the fact that once i click, all button become red or green. I tried to store the index in a state and compare the real index, but it doesn't work. here is my code:
in the main APP.js
const [clickedOne, setClickedOne] = useState({
clickedIndex: null,
});
useEffect(() => {
grabData();
}, []);
const handleClick = (choice, ke) => {
setChoice(choice);
if (choice === data.correct_answer) {
setIsCorrect(true);
} else {
setIsCorrect(false);
}
setClickedOne({ clickedIndex: ke });
grabData();
};
The mapped button inside the Render:
{answers.map((answer, index) => {
return (
<ContainedButtons
choice={handleClick}
answer={answer}
correct={data.correct_answer}
isCorrect={isCorrect}
key={index}
id={index}
clicked={clickedOne}
/>
);
})}
Inside the Button component:
const backStyle = () => {
if (clicked === id) {
if (isCorrect) {
return "green";
} else if (isCorrect === false) {
return "red";
} else {
return null;
}
}
};
return (
<div className={classes.root}>
<Button
style={{ backgroundColor: backStyle() }}
value={answer}
onClick={() => choice(answer, id)}
variant="contained"
>
{decodeURIComponent(answer)}
</Button>
When i check now inside the backstyle function if the clicked===id, now nothing happens anymore. Without that if check, i would have all buttons red or green.
Thank you guys for the help!
I have looked at your codesandbox demo, there are alot of other problems apart from the one your question is about.
First of all, each time you make a request to the API to fetch next question, you are making a request to get 10 questions instead of 1. API request URL contains a query parameter named amount which determines how many questions will be fetched on each request. Change its value to 1.
"https://opentdb.com/api.php?amount=1&encode=url3986"
Secondly, there is a lot of unnecessary code and unnecessary use of useState hook. You only need 2 things to be stored in the state, data and answers
const [data, setData] = useState({});
const [answers, setAnswers] = useState([]);
Now, coming to the original problem of detecting which button is clicked and correctly updating its background color.
To achieve the desired functionality, take following steps:
create couple of CSS classes as shown below
button.bgGreen {
background-color: green !important;
}
button.bgRed {
background-color: red !important;
}
pass a handleClick function from App component to ContainedButtons component. When a button is clicked, this click handler will be invoked. Inside the handleClick function, get the text and the button that was clicked using Event.target and depending on whether user answered correctly or not, add appropriate CSS class, created in step 1, on the button that was clicked.
Instead of using index as key for ContainedButtons in map function, use something that will be unique each time. This is needed because we want React to not re-use the ContainedButtons because if React re-uses the ContainedButtons component, then CSS classes added in step 2 will not be removed from the button.
Here's a working codesanbox demo of your app with the above mentioned steps.
In this demo, i have removed the unnecessary code and also changed the key of ContainedButtons inside map function to key={answer.length * Math.random() * 100}. You can change it to anything that will ensure that this key will be unique each time.

Cells overlapping problems in react virtualized with cell measurer

I am using React Virtualized to window my items list which has the following functionality
1) onClicking on any item, the user will be prompted with a details panel where he will be able to update the details of the item. And when he goes back to the list, he will be able to see the details in the item cell in the list. He can add a lot of details to make it bigger or reduce the size by removing the details
2) He can delete the item, or add another item to the list with certain details or without details.
CellMeasurer serves my requirement as dynamic height is supported. But I am having following issues with it
1) initially when my list mounts for the first time, first few items are measured and shown correctly but as soon as I scroll to the end, items get overlapped with each other.(positining isnt correct, I am guessing the defaultHeight is being applied to the unmeasured cells). This works fine as soon as the list is rerendered again.
2) Also, when I am updating the details of an item the list doesnt update with the new height adjustments
I am sure that somewhere my implementation is incorrect but have spent a lot of time hammering my head for it. Please let me know what can be done here to fix this
ItemView = props => {
const {index, isScrolling, key, style} = props,
items = this.getFormattedItems()[index]
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
isScrolling={isScrolling}
key={key}
rowIndex={index}
parent={parent}>
{({measure, registerChild}) => (
<div key={key} style={style} onLoad={measure}>
<div
onLoad={measure}
ref={registerChild}
{...parentProps}>
<Item
onLoad={measure}
key={annotation.getId()}
{...childProps}
/>
</div>
</div>
)}
</CellMeasurer>
)
}
renderDynamicListOfItems(){
return (<div>
<List
height={500}
ref={listComponent => (_this.listComponent = listComponent)}
style={{
width: '100%'
}}
onRowsRendered={()=>{}}
width={1000}
rowCount={props.items.length}
rowHeight={this._cache.rowHeight}
rowRenderer={memoize(this.ItemView)}
// onScroll={onChildScroll}
className={listClassName}
overscanRowCount={0}
/>
</div>
)
}
Also, I am manually triggering the remeasurement of my item in its componentDidUpdate like follows()
Component Item
...
componentDidUpdate() {
console.log('loading called for ', this.props.annotation.getId())
this.props.onLoad()
}
...
In the main parent I am recomputing the heights of the list every time the list has updated and triggering a forceupdate as follows
Component ParentList
...
componentDidUpdate() {
console.log("calling this parent recomputing")
this.listComponent.recomputeRowHeights()
this.listComponent.forceUpdateGrid()
}
...
I just faced the same issue and it turned out to be some layout updates in my Item component, that changed the height of the item after it has been measured by the CellMeasurer.
Fix was to pass down the measure function and call it on every layout update.
In my case it was
images being loaded
and text being manipulated with Twemoji (which replaces the emojis with images).
I also had problems getting the correct height from the cache after prepending some new list items when scrolling to the top of the list. This could be fixed by providing a keyMapper to the CellMeasurerCache:
const [cache, setCache] = useState<CellMeasurerCache>();
useEffect(() => {
setCache(new CellMeasurerCache({
keyMapper: (rowIndex: number, columnIndex: number) => listItems[rowIndex].id,
defaultHeight: 100,
fixedWidth: true,
}));
}, [listItems]);

Dynamically changing z-index for react-leaflet Tooltip

My issue is react-leaflet tooltips overlapping. When hovering over either a marker or the corresponding list on the right-hand side, the tooltip above it increases in size. However, I want the tooltip to appear layered above other tooltips.
I tried changing the style z-index in styling, but that doesn't work. I found a suggestion to change the Tooltip pane instead, but that didn't work either. It seems like changing the pane doesn't update.
Default pane reference: https://leafletjs.com/reference-1.5.0.html#map-pane
render() {
var job = this.props.job;
var icon_to_use = pointerIcon;
var css_class = 'tooltip-normal';
var paneToUse = 'tooltipPane';
if (this.props.selectedJob === this.props.id) {
css_class = 'tooltip-bold';
paneToUse = 'popupPane';
}
return (
<div>
<Marker position={[job.lat, job.lng]} pane={'markerPane'} icon={icon_to_use}>
<Tooltip permanent={true} pane={paneToUse} direction {'top'} offset={L.point(-10, -15)}>
<div className={css_class}>
{job.location}
</div>
</Tooltip>
</Marker>
</div>
)
}
The way I'm deciding which Tooltip should currently be active is by checking if this.props.selectedJob equals the current id. This works perfectly fine for assigning the css class in this line: css_class = 'tooltip-bold';, but not for the next line where I'm assigning the popupPane.
Even though I'm assigning the pane in the Tooltip component, they don't change in the actual application. Is there any way to dynamically make one tooltip overlay another based on hovering?
I found a solution, and I figure I'll post it here in case someone else comes across this. I simply had to set the tooltip className to a class with a certain index. Here's the basic idea (leaving out some unnecessary stuff):
if(condition){
tooltipCssClass = 'front-tooltip-class';
}
return (
<Tooltip permanent={true} className={tooltipCssClass} direction={'top'} offset={L.point(-10, -15)}>
...
</Tooltip>
)
CSS:
.back-tooltip-class {
z-index: -100;
}
.front-tooltip-class {
z-index: 100;
}

Categories

Resources