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