React - Button Pressed, keep calling function - javascript

I'm trying to implement a zoom function. onClick works fine, but I'd like to have it when I hold the zoom button down, it zooms continuously. How can I implement this with ReactJS?
Jquery: mousedown effect (while left click is held down)
I was using this as a template, but onMousedown doesn't get registered according to console.log
<div className="zoomControl" >
<button className="zoomIn" onMouseDown={this.zoomIn}>+</button>
<button className="zoomOut" onClick={this.zoomOut}>-</button>
</div>
zoomIn = () => {
console.log('test');
var self = this;
this.timeout = setInterval(function(){
// Do something continuously
this.renderer.zoomIn();
}, 100);
return false;
};
zoomMouseUp = () => {
clearInterval(this.timeout);
return false;
};

You need to use both mouseUp and mouseDown. Start a time on mouseDown and call the zoom function with the timeout repeatedly and clear the time on mouseUp.
Here a demo with zoomIn and zoomOut to compare and better understand the algorithm.
Hope this helps!!
class Zoom extends React.Component {
constructor(props) {
super(props)
this.state = {
zoom: 1,
}
this.t = undefined
this.start = 100
this.repeat = this.repeat.bind(this)
this.onMouseDown = this.onMouseDown.bind(this)
this.onMouseUp = this.onMouseUp.bind(this)
this.zoom = this.zoom.bind(this)
this.zoomOut = this.zoomOut.bind(this)
}
zoom(){
this.setState({zoom: this.state.zoom + 0.1})
}
repeat() {
this.zoom()
this.t = setTimeout(this.repeat, this.start)
this.start = this.start / 2
}
onMouseDown() {
this.repeat()
}
onMouseUp() {
clearTimeout(this.t)
this.start = 100
}
zoomOut(){
this.setState({
zoom: 1
})
}
render() {
return <div className="zoomControl" >
<div className="zoom" style={{transform: 'scale('+ this.state.zoom +')'}}></div>
<button className="zoomIn" onMouseUp={this.onMouseUp} onMouseDown={this.onMouseDown}>+</button>
<button className="zoomOut" onClick={this.zoomOut}>-</button>
</div>
}
}
ReactDOM.render(<Zoom/>, document.getElementById('app'))
body {
overflow: hidden
}
.zoom {
width: 20px;
height: 20px;
margin: 0 auto;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

If you have to do some kind of animation here, you're better off using requestAnimationFrame than setting intervals. I'd do it something like this.
class App extends React.Component {
state = {
value: 0,
mousedown: false
}
zoom = () => {
if (this.state.mousedown) {
this.setState({ value: this.state.value + 1},
() => { window.requestAnimationFrame(this.zoom) }
)
}
}
zoomIn = () => {
window.requestAnimationFrame(this.zoom);
}
toggleMouseDown = () => {
this.setState({
mousedown: !this.state.mousedown
});
this.zoomIn()
}
render() {
return(
<div>
<button
onMouseUp={this.toggleMouseDown}
onMouseDown={this.toggleMouseDown}>
Click me
</button>
{/* The rest of your component goes here */}
</div>
);
}
}

It's hard to get all of the context, but I'll try to give a relevant answer:
You don't have any property set to call zoomMouseUp when you release the button. I'd start with:
<button className="zoomIn" onMouseDown={this.zoomIn} onMouseUp={this.zoomMouseUp} onMouseOut={this.zoomMouseUp}>+</button>
You stated that it starts zooming, but doesn't stop. That makes me assume it's working, so that should probably fix it. I added the onMouseOut because if they press the button and move the mouse away without releasing it, it's going to continue.
There are a lot of ways to do this, but that's probably the most simple with what you have.

My issue was due to right click being the primary click or some thing along the lines. It works fine as is.

Related

How to render a self-destructing paragraph at mouse position in React?

I am trying to get a paragraph to appear at the location of the mouse coordinates, but self-destruct after 1 second.
$(function(){
var fadeDelay = 1000;
var fadeDuration = 1000;
$(document).click(function(e){
var div = $('<div class="image-wrapper">')
.css({
"left": e.pageX + 'px',
"top": e.pageY + 'px'
})
.append($('<img src="" alt="myimage" />'))
.appendTo(document.body);
setTimeout(function() {
div.addClass('fade-out');
setTimeout(function() { div.remove(); }, fadeDuration);
}, fadeDelay);
});
});
The code above is from a fiddle which represents the effect that I am looking for; however, it uses jQuery - while I am working with React.
I tried following this linear process:
1 - In the state, toggle a boolean with mouse clicks
playerAttack = () => {
this.setState({ hasPlayerAttacked: true })
}
2 - In a function, when the boolean is true, return a paragraph and set the boolean back to false
renderDamageDealtParagraph = () => {
if (this.state.hasPlayerAttacked) {
return <p>{this.state.playerAttack}</p>;
this.setState({ hasPlayerAttacked: false });
}
};
However, with this approach there were too many fallacies; main one being that upon resetting the boolean back to false, the rendered paragraph immediately disappears (instead of after a timeout of 1000ms).
What is the best wait to implement something like the linked fiddle, in ReactJS using vanilla JS?
Thanks in advance to whoever might be able to help.
You can basically do something like this:
Have state to track the mouse position x and y, and two booleans isShown and shouldHide to coordinate the disappering div
On click, show the div by setting isShown to true and immediately setTimeout to start hiding it in the future by adding a class by flipping the shouldHide to true
Once the class is added, the element will fade and will trigger the transitionend event at which point you can remove the div entirely by flipping the isShown to false and shouldHide to false boolean
Sample Implementation (Sorry for the shitty code, been a while since I React-ed)
JS Fiddle
class SelfDestructDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0,
isShown: false,
shouldHide: false
};
this.handleClick = this.handleClick.bind(this);
this.reset = this.reset.bind(this);
}
reset() {
this.setState({
x: 0,
y: 0,
isShown: false,
shouldHide: false
});
}
handleClick(event) {
if (this.state.isShown) {
return;
}
const { clientX, clientY } = event;
this.setState({
x: clientX,
y: clientY,
isShown: true,
shouldHide: false
});
setTimeout(() => {
this.setState({ shouldHide: true });
}, 1000);
}
render() {
const p = this.state.isShown && (
<div
onTransitionEnd={this.reset}
className={`${this.state.shouldHide ? "should-hide" : ""} modal`}
style={{ top: this.state.y, left: this.state.x }}
>
Self destructing....
</div>
);
return (
<div className="container" onClick={this.handleClick}>
{p}
</div>
);
}
}
ReactDOM.render(<SelfDestructDemo />, document.querySelector("#app"));

React - Prevent onMouseDown event on parent element when a child element is clicked

I have a parent element with an onMouseDown event that I use to make it draggable.
Within this I have several absolutely positioned child elements with onClick events.
I had thought that by applying a higher Z-index to the child elements the onClick would be run rather than the onMouseDown but it seems that both run and if the div has been dragged the onClick event will not run properly. I've tried to stop the events propagating but it doesn't seem to help. How can I prevent the parent onMouseDown being activated when the onClick on the child runs?
This is a simplified version of the code
export default class Day extends Component {
constructor(props) {
super(props);
this.showTalks = this.showTalks.bind(this)
this.state = {isScrolling: false};
this.componentWillUpdate = this.componentWillUpdate.bind(this)
this.toggleScrolling = this.toggleScrolling.bind(this)
this.onScroll = this.onScroll.bind(this)
this.onMouseMove = this.onMouseMove.bind(this)
this.onMouseUp = this.onMouseUp.bind(this)
this.onMouseDown = this.onMouseDown.bind(this)
this.attachScroller = this.attachScroller.bind(this)
this.titleRef = React.createRef();
}
componentWillUpdate = (nextProps, nextState) =>{
if(this.state.isScrolling !== nextState.isScrolling ) {
this.toggleScrolling(nextState.isScrolling);
}
};
toggleScrolling = (isEnable) => {
if (isEnable) {
window.addEventListener('mousemove', this.onMouseMove);
window.addEventListener('mouseup', this.onMouseUp);
window.addEventListener("drag", this.onMouseMove);
} else {
window.removeEventListener('mousemove', this.onMouseMove);
window.removeEventListener("drag", this.onMouseMove);
}
};
onScroll = (event) => {
};
onMouseMove = (event) => {
const {clientX, scrollLeft, scrollTop, clientY} = this.state;
this._scroller.scrollLeft = -(scrollLeft - clientX + event.clientX);
this._scroller.scrollTop = scrollTop - clientY + event.clientY;
};
onMouseUp = () => {
this.setState(
{
isScrolling: false,
scrollLeft: 0,
scrollTop: 0,
clientX: 0,
clientY:0
}
);
};
onMouseDown = (event) => {
const {scrollLeft, scrollTop} = this._scroller;
this.setState({
isScrolling:true,
scrollLeft,
scrollTop,
clientX:event.clientX,
clientY:event.clientY
});
};
attachScroller = (scroller) => {
this._scroller = ReactDOM.findDOMNode(scroller);
};
showTalks(talkApi){
this.props.showTalks( talkApi);
}
render() {
let talksArray = [<Talk></Talk>] //removed code that generates an array of talks
return (
<div
onMouseDown={this.onMouseDown}
onScroll={this.onMouseMove}
className="multi-columns">
{talksArray}
</div>
);
}
}
export default class Talk extends Component {
constructor(props) {
super(props);
this.handleFetch = this.handleFetch.bind(this);
}
handleFetch(e){
e.preventDefault();
e.stopPropagation();
let id = e.currentTarget.dataset.id;
this.props.showTalks(id);
}
render(){
return(
<div
data-id={this.props.id}
onClick={this.handleFetch.bind(this)}
>
<h2>{this.props.title}</h2>
</div>
)
}
}
I realised that e.stopPropagation(); on the handleFetch() function doesn't work because I'm using an onClick event and stopping the propagation of that has no effect on a totally seperate onMouseDown event.
For now I'm switching the onClick to another onMouseDown event and then the stopPropagation works. But I'd be interested if there is a better way to allow me ot use onClick.

Javascript mouseDown - cannot read currentTarget of undefined

I have a composent with which I would allow maintaining click in order to call multiple function by push-holding. My action dispatch a simple function to Redux reducers.
The objective of my component is to allow people decrease quantity of their order by maintaining a mouse's click. So that it, to allowing visitors have a more fluent user experience.
When I trigger the function my console returns me :
Cannot read property 'currentTarget' of undefined
When I click alone one time it is great. But when I mouseDown it fails with the above message.
Here my reactComponent.js:
import React, {Component} from 'react'
import style from "./OrderRibbon.css";
import equal from 'deep-equal';
export default class OrderRibbon extends Component {
t;
start = 100;
decreaseQuantity = (e) => {
e.preventDefault();
this.props.decreaseOrder(this.props.id)
}
addOrder= (e) => {
e.preventDefault();
this.props.addOrder(this.props.id)
}
orderPushing = (e) => {
e.preventDefault();
this.orderRepeat(e);
}
orderRepeat = (e) => {
if( e.currentTarget.attributes.name.value ){
console.log("EVENT NAME IN ORDER REAPEAT: ", e.currentTarget.attributes.name.value)
}else{
console.log("EVENT NAME IN ORDER REAPEAT: ", e.target.attributes.name.value)
}
if(e.currentTarget.attributes.name.value === "addOrder"){
this.addOrder
}else{
this.decreaseQuantity
}
this.t = setTimeout(this.orderRepeat, this.start);
this.start = this.start / 2;
}
// STOP Calling function
onMouseUp = () => {
clearTimeout(this.t);
this.start = 100;
}
render(){
return (
<div className={style.order_ribbon_layout} >
<div className={`${style.title} ${style.details_element}`} >
{this.props.title}
<div className={style.quantity} >
<div className= {style.quantity_icon}></div>
<span className= {style.quantity_number} > {this.props.quantity} </span>
</div>
</div>
<div className={style.price} >
{this.props.price * this.props.quantity}
</div>
<div className={style.quantity} >
<div
onMouseUp={this.onMouseUp}
onMouseDown={this.orderPushing}
name="decreaseQuantity"
onClick={this.decreaseQuantity}
className={ `${style.cardButton}`}
id={style.decreaseQuantity}></div>
<div
onMouseUp={this.onMouseUp}
onMouseDown={this.orderPushing}
name="addOrder"
onClick={this.addOrder}
className={ `${style.addButon}`}
// ${style.details_element}
id={style.addArticle}></div>
</div>
</div>
)
}
}
I wcan't figure out what is going wrong, if any body have an hint, would be great.
You have event binding issue. You can define like this:
orderPushing = () => (e) => {
e.preventDefault();
this.orderRepeat(e);
}
Or, keeping the same as you currently have, you may use inline event binding like this:
onMouseDown={(e) => this.orderPushing(e)}

Set a single setTimeout in React

I'm building a multiplication testing app, and I want each question to have an 8-second countdown that resets when an answer is ended. My problem is that when my component re-renders another instance of the setTimeout is created, meaning I get a two-second countdown, then 3 second etc.
What would be the correct way to handle this?
My component:
class Question extends Component {
constructor(props){
super(props);
this.state = {
answer: "",
correct: false,
timer: 8
}
this.countDownTrack = null;
this.checkAnswer = this.checkAnswer.bind(this);
}
countDown() {
let number = this.state.timer;
if (number > 0 && this.state.correct === false){
number--
this.setState({
timer: number
})
} else {
this.props.sendAnswer(this.state.answer, this.props.randIndex);
this.setState({
timer: 8
})
}
}
//what we want is for there to be a single setTimeout
CountDownHandle = () => {
this.countDownTrack = setTimeout(()=>{
this.countDown();
},1000)
}
checkAnswer = (e) => {
e.preventDefault();
this.props.sendAnswer(this.state.answer, this.props.randIndex);
this.setState({
answer: "",
timer: 8
})
}
handleChange = (event) => {
this.setState({
answer: event.target.value
})
}
render(){
this.CountDownHandle();
let countShow;
countShow = this.state.timer;
return (
<div className="Question">
<h1>What is</h1>
<h1><span id="table1">{parseInt(this.props.no1)}</span> X <span id="table2">{parseInt(this.props.no2)}</span>?</h1>
<form action="" onSubmit={this.checkAnswer}>
<input autoComplete="off" type="text" name="answer" onChange={this.handleChange} value={this.state.answer}/>
</form>
<p>{countShow}</p>
</div>
);
}
}
export default Question;
you need to clear timeout re-rendering the component using this function:
clearTimeout(this.trackingTimer);
It maybe a part of else code block in countDown().
Remember to clear the timeout when the component is unmount via ComponentWillUnmount.
That will make sure there is no unwanted timer running on the background.
2 issues with your code structure:
If you want to show a visible countdown (eg. 8,7,6,...)
use "setInterval"
rather than "setTimeout"
Place the timer event trigger function outside of the render function, like in the componentDidMount, a button click, input focus, or a response to a singular action.
Also, to control the timeout to be singular, check if an instance exists 1st.
if(!this.countDownTrack)
this.countDownTrack = setInterval(
()=>{
this.countDown();
if(this.state.timer < 1)
clearInterval(this.trackingTimer);
},1000)

How to trigger a vue method only once, not every time

I'm handling a rotate even on change:
<div #change="handleRotate"></div>
<script>
export default {
data: {
rotate = 0
},
methods: {
handleRotate () {
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY)
}
}
}
</script>
Right now, the second this.rotate runs on every change. How can I do it so that the second this.rotate is applied only the first time handleRotate runs?
Solving it Vue way:
You can use $once, which will listen for a event but only once.
Listen for a custom event, but only once. The listener will be removed once it triggers for the first time.
You just need to add .once to #change like following:
<div #change.once="handleRotate"></div>
<script>
export default {
//No Change here
}
</script>
Check demo if this in the fiddle.
Old Answer:
If you do not want to have initial value set for rotate, you can have one more variable : hasRotated to track whether rotate has been changed or not. Initially set hasRotated to true, once rotate has been changed set hasRotated to false, like following:
<div #change="handleRotate"></div>
<script>
export default {
data: {
rotate: 123,
hasRotated: false
},
methods: {
handleRotate () {
if(this.hasRotated){
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY)
this.hasRotated = false
}
}
}
}
</script>
one simple solution would be to add a marker somewhat like this:
<script>
export default {
data: {
rotate = 0
},
methods: {
handleRotate () {
if(!this.rotated){
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY);
this.rotated = true;
}
}
}
}
</script>
of course you would need to initiate this.rotated as false
If rotate start always at zero you can do:
export default {
data: {
rotate = 0
},
methods: {
handleRotate(e) {
if (this.rotate !== 0) {
return;
}
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY);
}
}
};

Categories

Resources