const Index = () => {
// Ref Links
const frefLinks = {
1: useRef(1),
2: useRef(2),
3: useRef(3),
4: useRef(4),
5: useRef(5),
};
const scrollLink = (i) => {
let frefLink = frefLinks[i];
return frefLink.current.scrollIntoView({
block: "start",
behavior: "smooth"
});
};
return (
<React.Fragment>
<style.globalStyle/>
<NavMenu
scrollToLinkA={() => { scrollLink(1) }}
scrollToLinkB={() => { scrollLink(2) }}
scrollToLinkC={() => { scrollLink(3) }}
scrollToLinkD={() => { scrollLink(4) }}
scrollToLinkE={() => { scrollLink(5) }}
/>
<Sect1 fref={frefLinks[1]} onWheel={e => e.deltaY <= 0 ? console.log('up') : scrollLink(2) }/>
<Sect2 fref={frefLinks[2]} onWheel={e => e.deltaY <= 0 ? scrollLink(1) : scrollLink(3) }/>
<Sect3 fref={frefLinks[3]} onWheel={e => e.deltaY <= 0 ? () => { scrollLink(2) } : () => { scrollLink(4) } }/>
<Sect4 fref={frefLinks[4]} onWheel={e => e.deltaY <= 0 ? scrollLink(3) :scrollLink(5) }/>
<Sect5 fref={frefLinks[5]} onWheel={e => e.deltaY <= 0 ? scrollLink(4) :console.log('down')}/>
</React.Fragment>
);
}
scrollToLinkA to E are nested div's onClick event. It works perfectly for onclick, onWheel is correct in the Section's nested div. div onWheel = {onwheel} However, onwheel event is not firing the scroll function though console.log still works. I tried both normal calling the function and via arrow at Sect3's onwheel but it still fails.
I searched hard enough on react+onwheel+scrollintoview but I really cannot find any applicable answers. I'm not looking into onscroll, just onwheel is what i want.
I really want to reuse the scrollLink function and not adding extra window listener event or dependency. Please advise.
I can't tell if scrollLink function is not calling by onWheel, but I can be 100% sure that scrollLink is working properly as it is from onClick. I put console.log inside scrollLink and it logs too.
I have nav and section. nav's div's onClick fires scrollLink function and scrollintoview works perfectly. Now I'm doing section's div's onWheel event to fire the same 'scrollLink' function, it is not responding but console.log is working everywhere.
I can't post comment anymore so I'm posting here.
Related
I am working on a React web app where I have a div of a 4x4 grid which has 16 blocks rendered in them. Where I have arrow click events for right, left, up, and down arrow. I need to add the mobile touch support for those arrow clicks when it's accessed only on mobile devices. I have never implemented these touch/swipe mobile features hence it seems very confusing. Any help would be appreciated.
Component:
const Grid = () => {
const [grid, setGrid] = useState(new Grid());
const arrowLeftKey = 37;
const arrowDownKey = 40;
const handleKeyDown = (event) => {
if (grid.hasWon()) {
return;
}
if (event.keyCode >= arrowLeftKey && event.keyCode <= arrowDownKey) {
let direction = event.keyCode - arrowLeftKey;
let gridClone = Object.assign(
Object.create(Object.getPrototypeOf(grid)),
grid
);
let newGrid = gridClone.move(direction);
setGrid(newGrid);
}
};
useArrowKeyEvent('keydown',handleKeyDown); //hook
const displayBlocks = grid.cells.map((row, rowIndex) => {
return (
<div key={rowIndex}>
{row.map((col, colIndex) => {
return <Block key={rowIndex + colIndex} />;
})}
</div>
);
});
return (
<div className="grid" id="gridId">
{displayBlocks}
</div>
);
I came to know from googling that I would need to use Touch Events, such as touchStart, touchMove, touchEnd. Looking at the touchevents documentation I added the following piece of code to my component. I changed the MouseEvents to ´KeyBoardevent´. Since it's a arrow key click/keydown event. But this is not working. Not sure where am I doing wrong.
const onTouch = (evt) => {
evt.preventDefault();
if (evt.touches.length > 1 || (evt.type === "touchend" && evt.touches.length > 0))
return;
var newEvt = document.createEvent("KeyboardEvent");
var type = null;
var touch = null;
// eslint-disable-next-line default-case
switch (evt.type) {
case "touchstart":
type = "keydown";
touch = evt.changedTouches[0];
break;
case "touchmove":
type = "keydown";
touch = evt.changedTouches[0];
break;
case "touchend":
type = "keydown";
touch = evt.changedTouches[0];
break;
}
newEvt.initEvent(type, true, true, evt.originalTarget.ownerDocument.defaultView, 0,
touch.screenX, touch.screenY, touch.clientX, touch.clientY,
evt.keyCode('37'), evt.keyCode('39'), evt.keyCode('38'), evt.keyCode('40'), 0, null);
evt.originalTarget.dispatchEvent(newEvt);
}
document.addEventListener("touchstart", onTouch, true);
document.addEventListener("touchmove", onTouch, true);
document.addEventListener("touchend", onTouch, true);
I get the following error when I swipe right and expect for right arrow click:
TypeError: Cannot read property 'ownerDocument' of undefined
On the following line of code:
newEvt.initEvent(type, true, true, evt.originalTarget.ownerDocument.defaultView, 0,
touch.screenX, touch.screenY, touch.clientX, touch.clientY,
evt.keyCode('37'), evt.keyCode('39'), evt.keyCode('38'), evt.keyCode('40'), 0, null);
Version 2
Edit: : used react-swipeable after #sschwei1 suggested
I have added the following piece in the component :
const swipeHandlers = useSwipeable({
onSwipedLeft: useArrowKeyEvent('keydown',handleKeyDown),<<<<<problem
onSwipedRight: eventData => console.log("swiped right"),
onSwipedUp: eventData => console.log("swiped up"),
onSwipedDown: eventData => console.log("swiped down")
});
and the return statement:
<div className="grid" {...swipeHandlers}>
{displayBlocks}
</div>
Problem: Can't use the hook as callback function.
React-swipeable is a library which handles swipes for you, it enables you to create handlers for different swipe directions, e.g onSwipedLeft or onSwipedUp and pretty much all other cases you can think of like onTap, onSwiping, onSwiped, and many more.
In these handlers you can just re-use the logic of your arrow keys.
The first solution I would think of (not the prettiest solution, but easy to use and understand) to create a wrapper function for swipes and call the according keyHandler function to it
Here is an example of how these functions could look like:
const handleTouch = (key) => {
handleKeyDown({keyCode:key});
}
And in your touch handlers you can call this function with the according key
const swipeHandlers = useSwipeable({
onSwipedLeft: () => handleTouch('37'),
onSwipedUp: () => handleTouch('38'),
onSwipedRight: () => handleTouch('39'),
onSwipedDown: () => handleTouch('40')
});
Since you are only using the keyCode in your handleKeyDown function, you can just pass an object with the keyCode property to the function and 'simulate' the key press
How to make the touchMove event be interrupted if the finger goes beyond the bounds of the object to which the event is attached? And when interrupting, call another function.
I assume that I need to somehow determine the location of the object on which the event occurs and when exiting these coordinates somehow interrupt the event. But I can't find how to do this in React using useRef and how to interrupt the event.
const Scrollable = (props) => {
const items = props.items;
let ref = useRef();
const touchStarts = (e) => {...}
const touchEnd = (e) => {...}
const touchMove = (e) => {
if (ref && ref.current && !ref.current.contains(e.target)) {
return;
}
e.preventDefault();
...
}
useEffect(() => {
document.addEventListener("touchmove", touchMove);
...
return () => {
document.removeEventListener("touchmove", touchMove);
...
};
});
return (
<div>
<div
ref={ref}
onTouchStart={touchStarts}
onTouchMove={touchMove}
onTouchEnd={touchEnd}
>
</div>
</div>
);
}
const touchMove =(e) => {
//Inside the touch event, we use the ref hook to take the coordinates of the object.
let posa = ref.current.getBoundingClientRect();
//Next, we check the coordinates of the touch using clientX / Y checking that the touch is inside the object.
if(e.touches[0].clientX > posa.left &&
e.touches[0].clientX < posa.right &&
e.touches[0].clientY > posa.top &&
e.touches[0].clientY < posa.bottom ) {
ourFunc()
}
//If the condition is not met, we call the function that should be triggered when the finger is released.
else {
stopFunc()
}
at the current point, this code works, but when the user clicks to hide the menu, the useClickOutside fires too, the menu toggles off and on again... would there any way to fix that so when clicks outside it closes but when clicks the button it toggles on/off ?
const useClickOutside = (ref, handler) => {
useEffect(() => {
const clickHandler = (event) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener('mousedown', clickHandler);
return () => {
document.removeEventListener('mousedown', clickHandler);
};
});
};
const Settings = () => {
const ref = useRef();
const [toggle, setToggle] = useState(false);
useClickOutside(ref, () => setToggle(false));
return (
<div className='settings'>
<button onClick={() => setToggle(!toggle)} className='settings__button'>
Menu
</button>
{toggle && (
<div ref={ref} className='settings__panel'>
<Link className='settings__links' to='/user/settings'>
Your Profile
</Link>
<Link className='settings__links' to='/user/settings'>
Todos history
</Link>
<Link className='settings__links' to='/user/settings'>
Settings
</Link>
<Link className='settings__links' value={'Logout'} to='/user/login'>
Logout
</Link>
</div>
)}
</div>
);
};
You might consider adding a onBlur event on the .settings div with a tabIndex=0.
You can then then capture blurs of the div and test if the event came from within the div or not.
const onBlur = (e: FocusEvent < HTMLElement > ) => {
if (opened?) {
const element = e.relatedTarget;
if (element == null) {
// dropdown was blured because window lost focused. probably close.
} else if (element != e.currentTarget) {
if (!e.currentTarget.contains(element as Node)) {
// blured element is not in .settings. close
}
}
}
};
If you want to get fancy you can also add a keydown and close on escape.
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
// close!
}
);
Here is a code sandbox that implements these items.
You could make use of event stopPropagation. Add the call event.stopPropagation() to your onClick handler function that hides the menu.
<button
onClick={(e) => {
e.stopPropagation();
setToggle(!toggle);
}}
className='settings__button'
>
Menu
</button>
This will prevent that the onClick event bubbles upwards to the next event listener which would be your onClickOutside listener.
UPDATE:
This will only work if your event listener is listening for onClick events. Your inline onClick event listener will stop the propagation of the event of type click only.
document.addEventListener('click', clickHandler);
return () => {
document.removeEventListener('click', clickHandler);
};
I am trying to find a way to detect middle click event in React JS but so far haven't succeeded in doing so.
In Chrome React's Synthetic Click event does show the button clicked ->
mouseClickEvent.button === 0 // Left
mouseClickEvent.button === 1 // Middle but it does not execute the code at all
mouseClickEvent.button === 2 // Right (There is also onContextMenu with event.preventDefault() )
Please share your views.
If you are using a stateless component:
JS
const mouseDownHandler = ( event ) => {
if( event.button === 1 ) {
// do something on middle mouse button click
}
}
JSX
<div onMouseDown={mouseDownHandler}>Click me</div>
Hope this helps.
You can add a mouseDown event and then detect the middle button click like:
handleMouseDown = (event) => {
if(event.button === 1) {
// do something on middle mouse button click
}
}
You code might look like:
class App extends React.Component {
constructor() {
super();
this.onMouseDown = this.onMouseDown.bind(this);
}
componentDidMount() {
document.addEventListener('mousedown', this.onMouseDown);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.onMouseDown);
}
onMouseDown(event) {
if (event.button === 1) {
// do something on middle mouse button click
}
}
render() {
// ...
}
}
You can find more information on MouseEvent.button here:
https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
Be careful. Using mousedown won't always get you the behavior you want. A "click" is both a mousedown and a mouseup where the x and y values haven't changed. Ideally, your solution would store the x and y values on a mousedown and when mouseup occurs, you would measure to make sure they're in the same spot.
Even better than mousedown would be pointerdown. This configures compatibility with "touch" and "pen" events as well as "mouse" events. I highly recommend this method if pointer events are compatible with your app's compatible browsers.
The modern way of doing it is through the onAuxClick event:
import Card from 'react-bootstrap/Card';
import React, { Component } from 'react';
export class MyComponent extends Component {
onAuxClick(event) {
if (event.button === 1) {
// Middle mouse button has been clicked! Do what you will with it...
}
}
render() {
return (
<Card onAuxClick={this.onAuxClick.bind(this)}>
</Card>
);
}
You can use React Synthetic event as described below
<div tabIndex={1} onMouseDown={event => { console.log(event)}}>
Click me
</div>
You can keep onClick. In React, you have access to nativeEvent property from where you can read which button was pressed:
const clickHandler = (evt) => {
if (e.nativeEvent.button === 1) {
...
}
}
return (
<a onClick={clickHandler}>test</a>
)
My React app has overflow: hidden applied to the body for reasons of tranformations. This left the issue of not being able to register the scroll position of scrollY as scrolling happens in child components.
How can I apply window.scrollY or similar to register the scroll position of <div id="innerContainer">?
Here is a snippet of where one scroll addEventListener is creating a class on scroll. Problem is without registering the scrollY I cannot add the event.
componentDidMount () {
window.addEventListener('scroll', this.handleHeaderStuck);
}
componentWillUnmount () {
window.removeEventListener('scroll', this.handleHeaderStuck);
}
or
handleHeaderStuck() {
console.log('scrollPos', window.scrollY)
if (window.scrollY === 0 && this.state.isStuck === true) {
this.setState({isStuck: false});
}
else if (window.scrollY !== 0 && this.state.isStuck !== true) {
this.setState({isStuck: true});
}
}
and the general layout of is...
render() {
return (
<main className={this.state.isStuck ? 'header-stuck' : ''}>
<div id="container">
<header />
<div id="innerContainer">...</div>
<footer />
</div>
</main>
Update - after applying the answer submitted by Kingdaro:
Screenshot of console using the code submitted by Kingdaro that registers the scrollPos change but not the actual position
A ref should do the job here. Also make sure to unregister the event listener when the component unmounts, to avoid memory leaks.
class Example extends React.Component {
innerContainer = null
componentDidMount() {
this.innerContainer.addEventListener("scroll", this.handleHeaderStuck)
}
componentWillUnmount() {
this.innerContainer.removeEventListener("scroll", this.handleHeaderStuck)
}
// using a class property here so the `this` context remains properly bound
handleHeaderStuck = () => {
console.log('div scroll position:', this.innerContainer.scrollTop)
}
render() {
return <div id="innerContainer" ref={el => (this.innerContainer = el)} />
}
}