React js onMouseLeave not fire when mouse moves fast - reactjs

Have a list of following items:
function UserItem (props) {
const [showPic, setMode] = useState(false);
return (
<div>
<div
onMouseEnter={() => setMode(true)}
onMouseLeave={() => setMode(false)}
>
{showPic && <div>PIC</div>}
{!showPic && <div>{props.login}</div>}
</div>
</div>
)
}
The problem is that when mouse moved fast onMouseLeave not fire on all items. I understand it has something to do with delegation. But cannot figure out what exactly. Both event should fire on exact element they are attached to.
Any ideas?

I'm not sure if it's a bug. But can you try like this?
{showPic == true ? <div>PIC</div> : <div>{props.login}</div>}
If this still does not work then you should file a bug.

Related

React onClick event firing for child element

Click Handler.
const handleClick = (event) => {
console.log(event.target);
}
Return.
{ menu.map((menuItem, index) => (
<div onClick={handleClick}>
<div>{menuItem.text}</div>
<img src={menuItem.image} /></div>
))
}
My console tells me that the event.target is the IMG element instead of the DIV. Why is this happening and how can I lock the event to the parent DIV? Do I just have to write logic to look at the IMG parent element?
Thanks y'all.
this is because Bubbling and capturing. event.target is the “target” element that initiated the event. it's the event that you clicked on it and in this case, it was an image. I think you can get div by event.currentTarget because the handler runs on it.
Not sure about your code, but forcing the scope to the one of the div should work as well:
const handleClick = (event) => {
console.log(event.target);
}
{
menu.map((menuItem, index) => (
<div onClick={evt => handleClick(evt)}>
<div>{menuItem.text}</div>
<img src={menuItem.image} /></div>
))
}
Which is basically what you had, but I edited <div onClick={evt => handleClick(evt)}>.

How to display popups with time delay in react.js?

I have three custom popups and I am able to display them one after another with time delay.
But there is one scenario I am not able to resolve.There are 3 popups -
1)Welcome Video Popup 2)Profile Popup 3) Custom Notification Popup
I have a custom notification popup modal and this comes at the last before the two popups.
Now I want if there is no WelcomeVideo Popup and Profile popup and user hasn't allow or blocked notifications, my custom Notification Popup should show.
My Custom popup will only show if state setNotificationPopup is true.
Here is the code --
const handleCloseWelcomeMessagePopup = () => {
const firstTimeCheckIn = localStorage.getItem('firstTimeCheckIn');
if (firstTimeCheckIn !== 'true' && isShowWelcomeMessage) {
setTimeout(() => {
setNotificationPopup(true);
}, 5000);
}
};
const handleCloseProfilePopup = () => {
setTimeout(() => {
setNotificationPopup(true);
}, 5000);
setShowProfilePopup(false);
};
useEffect(() => {
const firstTimeCheckIn = localStorage.getItem('firstTimeCheckIn');
if (isAuthenticated && !isShowWelcomeMessage && isShowProfilePopup === false) {
if (firstTimeCheckIn !== null && firstTimeCheckIn !== true) {
setNotificationPopup(true);
}
}
}, []);
return (
{(welcomeMessage || welcomeMessageVideo) && (
<PopupModal
id="welcome-message"
headerText={
welcomeMessage && (
<p
className="fr-view welcome-header-text"
dangerouslySetInnerHTML={{
__html: welcomeMessage,
}}
/>
)
}
showModal={isShowWelcomeMessage}
onCloseFunc={() => {
handleCloseWelcomeMessagePopup();
}}
>
)}
{isShowProfilePopup && (
<PopupModal id="profile-popup" showModal={isShowProfilePopup} onCloseFunc={() => handleCloseProfilePopup()}>
<div>{<ProfileDetails params={params} isFirstTime hideProfilePopup={handleCloseProfilePopup} />}</div>
</PopupModal>
)}
{window?.Notification?.permission === 'default' && (
<PopupModal
id="otpPopup"
custClassName={'notification-popup'}
showModal={notificationPopup}
headerText={
<>
<div className="d-flex">
<Image
className=
'img-responsive notification-logo',
src={`${IMAGE_URL}${popupImage}`}
/>
<div>
<p className="notification-title">Test !</p>
<p className="notification-body">{`Allow notifications so you don't miss announcement or direct messages!`}</p>
</div>
</div>
</>
}
backdrop="static"
modelFooter={
<div className="notification-footer">
<Button className="btn btn-link" variant="secondary" onClick={closeNotification}>
Close
</Button>
<Button className="btn btn-primary" onClick={askNotificationPermission}>
Allow
</Button>
</div>
}
/>
)}
)
Note - The issue is if there is Profile Popup then my custom Notification Popup shows immediately after profile Popup and also after 5 sec.
Let's first understand how setTimeout works.
Javascript is a single-threaded language but not a browser. The browser is running three threads at a time: Js Engine, UI thread, and the timer thread. This timer thread actually controls the setTimeout.
Now when we are calling setTimeout, a timer thread starts the countdown, but our actual callback function does not come into execution before the other functions in the stack are not finished. So if there are other time-consuming functions being executed when time up, the callback of setTimeout will not finish in time.
Hence in your code also the code you wrote is correct the problem is with the use of setTimeout. Once we correct that our code will be executed as the way we want.
I hope this piece of information helped you.
For further understanding the concept of setTimeout please refer to the following link :-
https://www.javascripttutorial.net/javascript-bom/javascript-settimeout/
Maybe you should refactor you code as below.
there should be an array that contains all pop-ups that should be shown, in order.
Once this array is defined, your component will iterate through the array, showing popups.
Below is example
export const SomeComponent =(showPopup1,showPopup2,showPopup3)=> {
const popupsToShow = []
showPupup1 && popupsToShow.append(1)
showPupup2 && popupsToShow.append(2)
showPupup3 && popupsToShow.append(3)
useEffect(()=>{
for (i,e in popupsToShow){
setTimeout(() => {
showPopup(e)
}, i * 1000);
}
},[])
}

React onClick not firing for unkown reason

This react onClick doesn't seem to be firing. Seems perfectly normal, I've got no idea why.
console.log('rendered');
const [taskWindowState, setTaskWindowState] = React.useState<boolean>(false);
const setTaskState = (event) => {
event.preventDefault();
console.log('clicked');
setTaskWindowState(true);
}
return (
<div className={styles.Page}>
{taskWindowState ? <AddTask /> : null}
<div className={styles.PlusCircleHolder} onClick={setTaskState}>
<object type='image/svg+xml' data='/plus-circle.svg' className={styles.add}/>
</div>
<div className={styles.todo}>
</div>
</div>
)
it doesn't come up with 'clicked' in the console. I've checked that the div is covering the SVG completely, so I've been clicking on the div. It just doesn't seem to be firing for an unknown reason.
I fixed this issue by adding pointer-event: none to my css code for my svg.
#oliver, welcome. Please mark this as answer.
Update child with pointer-event: none and it will work.

Is there any pitfall of using ref as conditional statement?

Context: I am trying to scroll view to props.toBeExpandItem item which keeps changing depending on click event in parent component. Every time a user clicks on some button in parent component I want to show them this list and scroll in the clicked item to view port. Also I am trying to avoid adding ref to all the list items.
I am using react ref and want to add it conditionally only once in my component. My code goes as below. In all cases the option.id === props.toBeExpandItem would be truthy only once in loop at any given point of time. I want to understand will it add any overhead if I am adding ref=null for rest of the loop elements?
export const MyComponent = (
props,
) => {
const rootRef = useRef(null);
useEffect(() => {
if (props.toBeExpandItem && rootRef.current) {
setTimeout(() => {
rootRef.current?.scrollIntoView({ behavior: 'smooth' });
});
}
}, [props.toBeExpandItem]);
return (
<>
{props.values.map((option) => (
<div
key={option.id}
ref={option.id === props.toBeExpandItem ? rootRef : null}
>
{option.id}
</div>
))}
</>
);
};
Depending upon your recent comment, you can get the target from your click handler event. Will this work according to your ui?
const handleClick = (e) => {
e.target.scrollIntoView()
}
return (
<ul>
<li onClick={handleClick}>Milk</li>
<li onclick={handleClick}>Cheese </li>
</ul>
)

dynamically add item VS scroll behavior

As shown in the minimal example (gif), the scroll-behavior when adding items depends on the current scroll position.
Here is the code for that example:
const [items, setItems] = React.useState([]);
const addItem = i => {
setItems(s => [...s, i]);
};
return (
<div className="App">
<header style={boxStyle}>HEADER</header>
{items.map(item => (
<Item text={item} key={item} />
))}
<button onClick={() => addItem(`item${items.length}`)}>Add</button>
<footer style={boxStyle}>FOOTER</footer>
</div>
);
https://codesandbox.io/s/ancient-feather-lttw5?file=/src/App.js
For the first 2 items the add button moves down. But then the button feels like staying at the same position when new items are added before (effectively the scroll position changes what makes it feel like the item is added "before" rather than "in-place").
In our real world app that UX feels super stange, because each item is a big item-from with 10+ fields and the user would see only the bottom of that form. Instead, we would like the user to see the beginning of the form after clicking the add button (like in the example when adding item0 and item1 where the beginning of the item is shown where the mouse is).
Desired scroll UX
I found out that hiding the add button for a render after adding the item fixes the issue:
const [items, setItems] = React.useState([]);
const addItem = i => {
setHide(true);
setItems(s => [...s, i]);
setTimeout(() => setHide(false), 1);
};
const [hide, setHide] = React.useState(false);
return (
<div className="App">
<header style={boxStyle}>HEADER</header>
{items.map(item => (
<Item text={item} key={item} />
))}
{!hide && (
<button onClick={() => addItem(`item${items.length}`)}>Add</button>
)}
<footer style={boxStyle}>FOOTER</footer>
</div>
);
https://codesandbox.io/s/stupefied-kirch-6s5fs?file=/src/App.js
Question
Is there a (cross-browser (and ie11+)) way to achieve the desired behavior in a more elegant way? Ideally without manipulating the scroll position manually.
I think that your best candidate to do so is the method scrollIntoView(). Maybe when you click your button you can identify your item in the dom and scroll to it whenever the document is scrollable and the first item is out of view. Like:
const handleClick = (item) => {
const firstItem = undefined; //you should find the way to get the first item of your list in the dom
firstItem.scrollIntoView({ block: "start" }); // you can add some options for smoothess and position
addItem(item);
}
UPDATE:
Now that I understand the expected result clear, I still think that scrollIntoView is the solution, but in this case you will need to do it after item adding, you may need the hook useEffect for this, like:
useEffect(()=> {
const myAddButton = document.getElementById('myAddButton'); //get the button element
myButton.scrollIntoView({
block: "end"
});
}, [items])
/* Handle click is not needed anymore
const handleClick = (item) => {
addItem(item);
}*/
You can still tweak this a little bit so you can have a gap between the bottom of the page, but this will make a good start.

Resources