I'm trying a project where I use my handpose to play the dino game in chrome. It's been 6 hours and I cannot seem to find a good solution to passing props to the game. Here is the code of the App.js
function App() {
const [isTouching, setIsTouching] = useState(false);
const webcamRef = useRef(null);
const canvasRef = useRef(null);
const runHandpose = async () => {
const net = await handpose.load();
console.log('Handpose model loaded.');
// Loop and detect hands
setInterval(() => {
detect(net);
}, 100)
};
const detect = async (net) => {
if (
typeof webcamRef.current !== 'undefined' &&
webcamRef.current !== null &&
webcamRef.current.video.readyState === 4
) {
...
await handleDistance(hand);
}
}
const handleDistance = (predictions) => {
if (predictions.length > 0) {
predictions.forEach(async (prediction) => {
const landmarks = prediction.landmarks;
const thumbTipPoint = landmarks[4]
const indexFingerTipPoint = landmarks[8]
const xDiff = thumbTipPoint[0] - indexFingerTipPoint[0]
const yDiff = thumbTipPoint[1] - indexFingerTipPoint[1]
const dist = Math.sqrt(xDiff*xDiff + yDiff*yDiff)
if (dist < 35) {
setIsTouching(true);
} else {
setIsTouching(false);
}
})
}
}
useEffect(() => {
console.log(isTouching)
}, [isTouching])
useEffect(() => {
runHandpose();
}, [])
return (
<div className="App">
<div className="App-header">
<Webcam ref={webcamRef}
style={{...}}/>
<canvas ref={canvasRef}
style={{...}} />
</div>
<Game isTouching={isTouching} />
</div>
);
}
And here is some code from the game
export default function Game({ isTouching }) {
const worldRef = useRef();
const screenRef = useRef();
const groundRef1 = useRef();
const groundRef2 = useRef();
const dinoRef = useRef();
const scoreRef = useRef();
function setPixelToWorldScale() {
...
}
function handleStart() {
lastTime = null
speedScale = 1
score = 0
setupGround(groundRef1, groundRef2)
setupDino(dinoRef, isTouching)
setupCactus()
screenRef.current.classList.add("hide")
window.requestAnimationFrame(update)
}
async function update(time) {
if (lastTime == null) {
lastTime = time
window.requestAnimationFrame((time) => {
update(time)
})
return
}
const delta = time - lastTime
updateGround(delta, speedScale, groundRef1, groundRef2)
updateDino(delta, speedScale, dinoRef)
updateCactus(delta, speedScale, worldRef)
updateSpeedScale(delta)
updateScore(delta, scoreRef)
// if (checkLose()) return handleLose(dinoRef, screenRef)
lastTime = time
window.requestAnimationFrame((time) => {
update(time)
})
}
function updateSpeedScale(delta) {...}
function updateScore(delta) {...}
function checkLose() {...}
function isCollision(rect1, rect2) {...}
useEffect(() => {
console.log(isTouching)
window.requestAnimationFrame(update);
}, [isTouching])
function handleLose() {...}
useEffect(() => {
setPixelToWorldScale()
window.addEventListener("resize", setPixelToWorldScale())
document.addEventListener("click", handleStart, { once: true })
},[])
return (...);
}
What I've been trying to do is how I can pass isTouching to the game everytime my thumb and my index finger meet. But I want to avoid re-render the game and I only want to update the dino. But I cannot find a way to do that. I was also trying to create a isJump state inside the game but I don't know how to pass the setisJump to the parent (which is App) so that I can do something like this:
useEffect(() => {
setIsJump(true)
}, [isTouching])
Does anyone have a better idea of how to do this? Or did I made a mistake on passing the props here? Thank you
I'm trying to build a custom hook that has to alter values before using Formik's setFieldValue().
Here is how to hook looks like:
export const useCount = (tab) => {
const { values, setFieldValue } = useFormikContext();
const { count} = values;
useEffect(() => {
const handleChange = () => {
if (tab === 0) {
setFieldValue("count", count);
} else if (tab === 1) {
setFieldValue("count", count * 2);
}
};
handleChange();
}, [count]);
};
The issue at hand is that I end up with an infinite loop, due to the fact that I'm changing count inside the useEffect(). Any clues?
Replace count on tab in your useEffect, to trigger this effect only when tab is changed
export const useCount = tab => {
const {values: {count}, setFieldValue} = useFormikContext();
const handleChange = () => {
if (tab === 0) {
setFieldValue("count", count);
} else if (tab === 1) {
setFieldValue("count", count * 2);
}
}
useEffect(() => {
handleChange();
}, [tab]);
};
A popup is displayed when the add button is clicked and the count is greater or less than 0.
below is the code,
function AddButton () {
const [isOpen, setIsOpen] = React.useState(false);
const count = useGetCount();
useTrigger(isOpen, count);
const on_add_click = () => {
setIsOpen(true);
}
return (
<button onClick={on_add_click}>add</button>
);
}
interface ContextProps {
trigger: (count: number) => void;
}
const popupContext = React.createContext<ContextProps>({
trigger: (count: number) => {},
});
const usePopupContext = () => React.useContext(popupContext);
export const popupContextProvider = ({ children }: any) => {
const [show, setShow] = React.useState(false);
const limit = 0;
const dismiss = () => {
if (show) {
sessionStorage.setItem(somePopupId, 'dismissed');
setShow(false);
}
};
const isDismissed = (dialogId: string) =>
sessionStorage.getItem(dialogId) === 'dismissed';
const context = {
trigger: (count: number) => {
if (!isDismissed(somePopupId) && count <= limit) {
setShow(true);
} else if (count > limit) {
setShow(false);
}
},
};
return (
<popupContext.Provider value={context}>
{children}
{show && (
<Popup onHide={dismiss} />
)}
</popupContext.Provider>
);
};
export function useTrigger(enabled: boolean, count: number) {
const { trigger } = usePopupContext();
React.useEffect(() => {
if (enabled) {
trigger(count);
}
}, [enabled, count, trigger]);
}
This works but calls trigger method only when enabled is true.
I want to modify the above code such that when the user clicks the add button, I want this useTrigger to happen. I don't want to check for enabled and call trigger.
I have tried the following removed checking for enabled.
export function useTrigger(enabled: boolean, count: number) {
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}, [enabled, count, trigger]);
}
this works but the popup is displayed as the count is less than or equal to 0. but I want it to first check if the add button clicked or not.
so soon after the user clicking the add button in on_add_click I want the popup to display.
How can I modify the code above? I am new to using hooks. Could someone help me with this? thanks.
EDIT:strong text
i have tried to do something like below and i get error
Uncaught Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component.
on_add_click = () => {
Trigger(count);
}
export function Trigger(count: number) {
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}, [count, trigger]);
}
how can i fix this.
If I followed you right, you just need to add a state to your hook and return the setter to call it onclick:
export function useTrigger(count: number) {
const [clicked, setClicked] = React.useState(false)
const { trigger } = usePopupContext();
React.useEffect(() => {
if(clicked) {
trigger(count);
}
}, [count, trigger, clicked]);
const clickCb = useCallback(() => {setClicked(true)}, [])
return cb
}
Then in your component with button you do something like this:
const Component = (props) => {
const onClick = useTrigger(props.count)
/* ... */
return <button onClick={onClick}/>
}
I was using this code as reference to build a swipeable list of names https://gist.github.com/estaub/91e54880d77a9d6574b829cb0d3ba021
That code becomes a JSX component. I then had a parent component holding two lists, and the idea is to make an item from one of the lists go to the second list when we swipe it.
The code works fine, the problem started when I implemented the lists. When I swipe an Item, the parent component triggers a function that gets the swiped Item, removes from the first and add to the second list. The parent component gets rendered again. The problem is that the item below this one that was erased seems to be receiving the classes that was added to to make the item disappear. In other words, it's like if I was erasing two items. The erased item gets removed from the list, but the item below it still in the DOM now with the properties that hides it, so we can't see it anymore.
For some reason it seems like useRef is selecting the element below the one that has just been erased and applying the styles to it.
Any idea what I could try to solve it?
Here is my swipeable component
import React, {
useEffect, useRef,
} from 'react';
interface IOwnProps {
onSwipe?: () => void;
}
const SwipeableListItem:React.FC<IOwnProps> = ({
children, onSwipe,
}) => {
const listElementRef = useRef<HTMLDivElement | null>(null);
const wrapperRef = useRef<HTMLDivElement | null>(null);
const backgroundRef = useRef<HTMLDivElement | null>(null);
const dragStartXRef = useRef(0);
const leftRef = useRef(0);
const draggedRef = useRef(false);
useEffect(() => {
const onSwiped = () => {
if (onSwipe) {
onSwipe();
}
};
const onDragEnd = () => {
if (draggedRef.current) {
draggedRef.current = false;
const threshold = 0.3;
let elementOffsetWidth = 0;
if (listElementRef.current) {
elementOffsetWidth = listElementRef.current.offsetWidth;
}
if (leftRef.current < elementOffsetWidth * threshold * -1) {
leftRef.current = (-elementOffsetWidth * 2);
if (wrapperRef.current) {
wrapperRef.current.style.maxHeight = (0).toFixed(1);
}
onSwiped();
} else {
leftRef.current = 0;
}
if (listElementRef.current) {
listElementRef.current.className = 'BouncingListItem';
listElementRef.current.style.transform = `translateX(${leftRef.current}px)`;
}
}
};
const onDragEndMouse = (ev: MouseEvent) => {
window.removeEventListener('mousemove', onMouseMove);
onDragEnd();
};
const onDragEndTouch = (ev: TouchEvent) => {
window.removeEventListener('touchmove', onTouchMove);
onDragEnd();
};
window.addEventListener('mouseup', onDragEndMouse);
window.addEventListener('touchend', onDragEndTouch);
return () => {
window.removeEventListener('mouseup', onDragEndMouse);
window.removeEventListener('touchend', onDragEndTouch);
};
}, [onSwipe]);
const onDragStartMouse = (ev: React.MouseEvent) => {
onDragStart(ev.clientX);
window.addEventListener('mousemove', onMouseMove);
};
const onDragStartTouch = (ev: React.TouchEvent) => {
const touch = ev.targetTouches[0];
onDragStart(touch.clientX);
window.addEventListener('touchmove', onTouchMove);
};
const onDragStart = (clientX: number) => {
draggedRef.current = true;
dragStartXRef.current = clientX;
if (listElementRef.current) {
listElementRef.current.className = 'ListItem';
}
requestAnimationFrame(updatePosition);
};
const updatePosition = () => {
if (draggedRef.current) {
requestAnimationFrame(updatePosition);
}
if (listElementRef.current) {
listElementRef.current.style.transform = `translateX(${leftRef.current}px)`;
}
// fade effect
const opacity = (Math.abs(leftRef.current) / 100);
if (opacity < 1 && opacity.toString() !== backgroundRef.current?.style.opacity) {
if (backgroundRef.current) {
backgroundRef.current.style.opacity = opacity.toString();
}
}
};
const onMouseMove = (ev: MouseEvent) => {
const left = ev.clientX - dragStartXRef.current;
if (left < 0) {
leftRef.current = left;
}
};
const onTouchMove = (ev: TouchEvent) => {
const touch = ev.targetTouches[0];
const left = touch.clientX - dragStartXRef.current;
if (left < 0) {
leftRef.current = left;
}
};
return (
<div className="Wrapper" ref={wrapperRef}>
<div className="Background" ref={backgroundRef}>
<span>Remove</span>
</div>
<div
className="ListItem"
ref={listElementRef}
onMouseDown={onDragStartMouse}
onTouchStart={onDragStartTouch}
role="presentation"
>
{children}
</div>
</div>
);
};
export default SwipeableListItem;
Here is the parent component, where I handle the lists
import React, {
useEffect, useState,
} from 'react';
import SwipeableListItem from './SwipeableListItem';
interface ITest{
id:number;
}
interface IOwnProps{
list1?: ITest[];
list2?: ITest[];
}
const MyList: React.FC<IOwnProps> = ({
list1, list2,
}) => {
const [displayList1, setDisplayList1] = useState<boolean>(true);
const [list, setList] = useState<Array<Array<ITest>>>([[], []]);
useEffect(() => {
setList([list1 || [], list2 || []]);
}, [list1, list2]);
const handleSwitchList = () => {
setDisplayList1(!displayList1);
};
//without this function it works, but the item does not get removed from the list
const handleSendToSecondList = (id: number, myList1: ITest[], myList2: ITest[]) => {
const foundItem = myList2.find((s) => s.id === id);
const newList1 = myList1;
if (foundItem) {
// push item to list 1
newList1.push(foundItem);
// remove list 2
const newList2 = myList2.filter((s) => s.id !== foundItem.id);
setList([newList1, newList2]);
}
};
return (
<div>
<div>
<button
type="button"
className={`btn ${displayList1 ? 'btn-primary' : ''}`}
onClick={handleSwitchList}
>
List 1
</button>
<button
type="button"
className={`btn ${!displayList1 ? 'btn-primary' : ''}`}
onClick={handleSwitchList}
>
List 2
</button>
</div>
{
displayList1
? list[1]?.map((item) => (
<SwipeableListItem onSwipe={() => handleSendToSecondList(item.id, list[0], list[1])}>
<p>{item.id}</p>
</SwipeableListItem>
))
: list[0]?.map((item) => (
<SwipeableListItem>
<p>{item.id}</p>
</SwipeableListItem>
))
}
</div>
);
};
export default MyList;
I created this just to show what is happening
https://codesandbox.io/s/react-hooks-usestate-kbvqt?file=/src/index.js
The problem is happening here but a little different. There are two lists, if you swipe an item from the first list, an item from the second list will disappear, because it received the css properties.
I am building a Minesweeper game with React and want to perform a different action when a cell is single or double clicked. Currently, the onDoubleClick function will never fire, the alert from onClick is shown. If I remove the onClick handler, onDoubleClick works. Why don't both events work? Is it possible to have both events on an element?
/** #jsx React.DOM */
var Mine = React.createClass({
render: function(){
return (
<div className="mineBox" id={this.props.id} onDoubleClick={this.props.onDoubleClick} onClick={this.props.onClick}></div>
)
}
});
var MineRow = React.createClass({
render: function(){
var width = this.props.width,
row = [];
for (var i = 0; i < width; i++){
row.push(<Mine id={String(this.props.row + i)} boxClass={this.props.boxClass} onDoubleClick={this.props.onDoubleClick} onClick={this.props.onClick}/>)
}
return (
<div>{row}</div>
)
}
})
var MineSweeper = React.createClass({
handleDoubleClick: function(){
alert('Double Clicked');
},
handleClick: function(){
alert('Single Clicked');
},
render: function(){
var height = this.props.height,
table = [];
for (var i = 0; i < height; i++){
table.push(<MineRow width={this.props.width} row={String.fromCharCode(97 + i)} onDoubleClick={this.handleDoubleClick} onClick={this.handleClick}/>)
}
return (
<div>{table}</div>
)
}
})
var bombs = ['a0', 'b1', 'c2'];
React.renderComponent(<MineSweeper height={5} width={5} bombs={bombs}/>, document.getElementById('content'));
This is not a limitation of React, it is a limitation of the DOM's click and dblclick events. As suggested by Quirksmode's click documentation:
Don't register click and dblclick events on the same element: it's impossible to distinguish single-click events from click events that lead to a dblclick event.
For more current documentation, the W3C spec on the dblclick event states:
A user agent must dispatch this event when the primary button of a pointing device is clicked twice over an element.
A double click event necessarily happens after two click events.
Edit:
One more suggested read is jQuery's dblclick handler:
It is inadvisable to bind handlers to both the click and dblclick events for the same element. The sequence of events triggered varies from browser to browser, with some receiving two click events before the dblclick and others only one. Double-click sensitivity (maximum time between clicks that is detected as a double click) can vary by operating system and browser, and is often user-configurable.
Instead of using ondoubleclick, you can use event.detail to get the current click count. It's the number of time the mouse's been clicked in the same area in a short time.
const handleClick = (e) => {
switch (e.detail) {
case 1:
console.log("click");
break;
case 2:
console.log("double click");
break;
case 3:
console.log("triple click");
break;
}
};
return <button onClick={handleClick}>Click me</button>;
In the example above, if you triple click the button it will print all 3 cases:
click
double click
triple click
Live Demo
The required result can be achieved by providing a very slight delay on firing off the normal click action, which will be cancelled when the double click event will happen.
let timer = 0;
let delay = 200;
let prevent = false;
doClickAction() {
console.log(' click');
}
doDoubleClickAction() {
console.log('Double Click')
}
handleClick() {
let me = this;
timer = setTimeout(function() {
if (!prevent) {
me.doClickAction();
}
prevent = false;
}, delay);
}
handleDoubleClick(){
clearTimeout(timer);
prevent = true;
this.doDoubleClickAction();
}
< button onClick={this.handleClick.bind(this)}
onDoubleClick = {this.handleDoubleClick.bind(this)} > click me </button>
You can use a custom hook to handle simple click and double click like this :
import { useState, useEffect } from 'react';
function useSingleAndDoubleClick(actionSimpleClick, actionDoubleClick, delay = 250) {
const [click, setClick] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
// simple click
if (click === 1) actionSimpleClick();
setClick(0);
}, delay);
// the duration between this click and the previous one
// is less than the value of delay = double-click
if (click === 2) actionDoubleClick();
return () => clearTimeout(timer);
}, [click]);
return () => setClick(prev => prev + 1);
}
then in your component you can use :
const click = useSingleAndDoubleClick(callbackClick, callbackDoubleClick);
<button onClick={click}>clic</button>
Edit:
I've found that this is not an issue with React 0.15.3.
Original:
For React 0.13.3, here are two solutions.
1. ref callback
Note, even in the case of double-click, the single-click handler will be called twice (once for each click).
const ListItem = React.createClass({
handleClick() {
console.log('single click');
},
handleDoubleClick() {
console.log('double click');
},
refCallback(item) {
if (item) {
item.getDOMNode().ondblclick = this.handleDoubleClick;
}
},
render() {
return (
<div onClick={this.handleClick}
ref={this.refCallback}>
</div>
);
}
});
module.exports = ListItem;
2. lodash debounce
I had another solution that used lodash, but I abandoned it because of the complexity. The benefit of this was that "click" was only called once, and not at all in the case of "double-click".
import _ from 'lodash'
const ListItem = React.createClass({
handleClick(e) {
if (!this._delayedClick) {
this._delayedClick = _.debounce(this.doClick, 500);
}
if (this.clickedOnce) {
this._delayedClick.cancel();
this.clickedOnce = false;
console.log('double click');
} else {
this._delayedClick(e);
this.clickedOnce = true;
}
},
doClick(e) {
this.clickedOnce = undefined;
console.log('single click');
},
render() {
return (
<div onClick={this.handleClick}>
</div>
);
}
});
module.exports = ListItem;
on the soapbox
I appreciate the idea that double-click isn't something easily detected, but for better or worse it IS a paradigm that exists and one that users understand because of its prevalence in operating systems. Furthermore, it's a paradigm that modern browsers still support. Until such time that it is removed from the DOM specifications, my opinion is that React should support a functioning onDoubleClick prop alongside onClick. It's unfortunate that it seems they do not.
Here's what I have done. Any suggestions for improvement are welcome.
class DoubleClick extends React.Component {
state = {counter: 0}
handleClick = () => {
this.setState(state => ({
counter: this.state.counter + 1,
}))
}
handleDoubleClick = () => {
this.setState(state => ({
counter: this.state.counter - 2,
}))
}
render() {
return(
<>
<button onClick={this.handleClick} onDoubleClick={this.handleDoubleClick>
{this.state.counter}
</button>
</>
)
}
}
Typescript React hook to capture both single and double clicks, inspired by #erminea-nea 's answer:
import {useEffect, useState} from "react";
export function useSingleAndDoubleClick(
handleSingleClick: () => void,
handleDoubleClick: () => void,
delay = 250
) {
const [click, setClick] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
if (click === 1) {
handleSingleClick();
}
setClick(0);
}, delay);
if (click === 2) {
handleDoubleClick();
}
return () => clearTimeout(timer);
}, [click, handleSingleClick, handleDoubleClick, delay]);
return () => setClick(prev => prev + 1);
}
Usage:
<span onClick={useSingleAndDoubleClick(
() => console.log('single click'),
() => console.log('double click')
)}>click</span>
This is the solution of a like button with increment and discernment values based on solution of Erminea.
useEffect(() => {
let singleClickTimer;
if (clicks === 1) {
singleClickTimer = setTimeout(
() => {
handleClick();
setClicks(0);
}, 250);
} else if (clicks === 2) {
handleDoubleClick();
setClicks(0);
}
return () => clearTimeout(singleClickTimer);
}, [clicks]);
const handleClick = () => {
console.log('single click');
total = totalClicks + 1;
setTotalClicks(total);
}
const handleDoubleClick = () => {
console.log('double click');
if (total > 0) {
total = totalClicks - 1;
}
setTotalClicks(total);
}
return (
<div
className="likeButton"
onClick={() => setClicks(clicks + 1)}
>
Likes | {totalClicks}
</div>
)
Here is one way to achieve the same with promises. waitForDoubleClick returns a Promise which will resolve only if double click was not executed. Otherwise it will reject. Time can be adjusted.
async waitForDoubleClick() {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
if (!this.state.prevent) {
resolve(true);
} else {
reject(false);
}
}, 250);
this.setState({ ...this.state, timeout, prevent: false })
});
}
clearWaitForDoubleClick() {
clearTimeout(this.state.timeout);
this.setState({
prevent: true
});
}
async onMouseUp() {
try {
const wait = await this.waitForDoubleClick();
// Code for sinlge click goes here.
} catch (error) {
// Single click was prevented.
console.log(error)
}
}
Here's my solution for React in TypeScript:
import { debounce } from 'lodash';
const useManyClickHandlers = (...handlers: Array<(e: React.UIEvent<HTMLElement>) => void>) => {
const callEventHandler = (e: React.UIEvent<HTMLElement>) => {
if (e.detail <= 0) return;
const handler = handlers[e.detail - 1];
if (handler) {
handler(e);
}
};
const debounceHandler = debounce(function(e: React.UIEvent<HTMLElement>) {
callEventHandler(e);
}, 250);
return (e: React.UIEvent<HTMLElement>) => {
e.persist();
debounceHandler(e);
};
};
And an example use of this util:
const singleClickHandler = (e: React.UIEvent<HTMLElement>) => {
console.log('single click');
};
const doubleClickHandler = (e: React.UIEvent<HTMLElement>) => {
console.log('double click');
};
const clickHandler = useManyClickHandlers(singleClickHandler, doubleClickHandler);
// ...
<div onClick={clickHandler}>Click me!</div>
I've updated Erminea Nea solution with passing an original event so that you can stop propagation + in my case I needed to pass dynamic props to my 1-2 click handler. All credit goes to Erminea Nea.
Here is a hook I've come up with:
import { useState, useEffect } from 'react';
const initialState = {
click: 0,
props: undefined
}
function useSingleAndDoubleClick(actionSimpleClick, actionDoubleClick, delay = 250) {
const [state, setState] = useState(initialState);
useEffect(() => {
const timer = setTimeout(() => {
// simple click
if (state.click === 1) actionSimpleClick(state.props);
setState(initialState);
}, delay);
// the duration between this click and the previous one
// is less than the value of delay = double-click
if (state.click === 2) actionDoubleClick(state.props);
return () => clearTimeout(timer);
}, [state.click]);
return (e, props) => {
e.stopPropagation()
setState(prev => ({
click: prev.click + 1,
props
}))
}
}
export default useSingleAndDoubleClick
Usage in some component:
const onClick = useSingleAndDoubleClick(callbackClick, callbackDoubleClick)
<button onClick={onClick}>Click me</button>
or
<button onClick={e => onClick(e, someOtherProps)}>Click me</button>
import React, { useState } from "react";
const List = () => {
const [cv, uv] = useState("nice");
const ty = () => {
uv("bad");
};
return (
<>
<h1>{cv}</h1>
<button onDoubleClick={ty}>Click to change</button>
</>
);
};
export default List;