Prevent a double-click being interpreted as two single-clicks? - javascript

I have the following rather simple React Class:
import React from "react"
export default class DoubleClick extends React.Component {
constructor(props) {
super(props);
this.state = {
};
this.handleClick = this.handleClick.bind(this);
this.handleDoubleClick = this.handleDoubleClick.bind(this);
}
handleClick() {
console.log("Click");
}
handleDoubleClick() {
console.log("Double Click");
}
render() {
return (
<div style={{backgroundColor: 'pink'}}>
<div onClick={this.handleClick}>
<span onDoubleClick={this.handleDoubleClick}> Hello </span>
<span onDoubleClick={this.handleDoubleClick}> world. </span>
</div>
</div>
);
}
}
When somebody single-clicks on the outer div, I want to call handleClick, when somebody double-clicks on any of the inner spans I want to call handleDoubleClick, but not the handleClick for the outer div as well.
However, whenever I double-click, handleDoubleClick() is called, but handleClick is also called, namely twice.
I would like to call handleClick() only when I click, but do not double-click - is this possible?

I have a HOC that I use to approximate what you're looking for:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class DoubleClick extends Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.onDoubleClick = this.onDoubleClick.bind(this);
this.timeout = null;
}
onClick(e) {
e.preventDefault();
if(this.timeout === null) {
this.timeout = window.setTimeout(() => {
this.timeout = null;
this.props.onClick();
}, 300);
}
}
onDoubleClick(e) {
e.preventDefault();
window.clearTimeout(this.timeout);
this.timeout = null;
this.props.onDoubleClick();
}
render() {
const { onClick, onDoubleClick, children, ...childProps } = this.props;
const props = Object.assign(childProps, { onClick: this.onClick, onDoubleClick: this.onDoubleClick });
return React.cloneElement(children, props);
}
}
DoubleClick.propTypes = {
onClick: PropTypes.func.isRequired,
onDoubleClick: PropTypes.func.isRequired,
children: PropTypes.element.isRequired,
};
Used like this:
<DoubleClick onClick={clickHandler} onDoubleClick={doubleClickHandler}>
<button>Click or double click me</button>
</DoubleClick>
When the first click is received, it sets a 300ms timeout. If no second click is received within 300ms, the function in the onClick prop will be invoked. If a second click is received within 300ms, the function in the onDoubleClick prop will be invoked, and the timeout is canceled so the onClick does not fire.
Needless to say, this is an imperfect approximation, but I've found it to be a sufficiently satisfying user experience in practice.

Like others said, it's not possible to do a click event on the parent with a doubleClick on the children without a timeout (I think).
If you can sacrifice the UX part of the 500ms until the click is recognized as a non-doubleclick you can work with timeout.
constructor(props) {
super(props);
this.state = {
};
this.handleClick = this.handleClick.bind(this);
this.handleDoubleClick = this.handleDoubleClick.bind(this);
// hack doubleclick
this.doubleClickTimeout = 500; // default doubleclick timeout
this.clickedTimeout = null;
}
handleClick(ev) {
if (!this.clickedTimeout) {
this.clickedTimeout = setTimeout(() => {
this.clickedTimeout = null;
// do your stuff here
console.log('Clicked');
}, this.doubleClickTimeout);
}
}
handleDoubleClick() {
clearTimeout(this.clickedTimeout);
this.clickedTimeout = null;
console.log("Double Click");
}
render() {
return (
<div style={{backgroundColor: 'pink'}}>
<div onClick={this.handleClick}>
<span onDoubleClick={this.handleDoubleClick}> Hello </span>
<span onDoubleClick={this.handleDoubleClick}> world. </span>
</div>
</div>
);
}

Related

how to call multiple methods in onClick in react?

I have two components (Parent component & Child component) in my react app. I have two button clicks in my child component and I need to pass two props to the parent component. I use the code as follows.
The problem is, I can't include both methods in the parent component's element, but I need to. How can I use both edituser and deleteuser functions in the parent component?
Child component:
class EnhancedTable extends React.Component {
constructor(props){
super(props);
this.state = {
userID: 10
};
this.editByUserId = this.sendUserId.bind(this);
this.DeleteByUserId = this.sendUserId.bind(this);
}
editByUserId() {
this.props.onClick(this.state.userID);
}
DeleteByUserId() {
this.props.onClick(this.state.userID);
}
render() {
return (
<button onClick={this.sendUserId}>
<BorderColorIcon onClick={this.editUserById} className="action margin-r" />
<DeleteIcon onClick={this.deleteUserById} className="action margin-r" />
</button>
)
}
}
Parent component:
Import EnhancedTable from './EnhancedTable';
class Users extends Component {
constructor(props) {
super(props);
this.state = {
userID: null
};
this.editUser = this.editUser.bind(this);
this.deleteUser = this.deleteUser.bind(this);
}
editUser(idd) {
this.setState({
userID : idd
})
console.log("User Edited");
}
deleteUser(idd) {
this.setState({
userID : idd
})
console.log("User Deleted");
}
render() {
return(
<EnhancedTable onClick = {(e)=>{this.editUser; this.deleteUser;}}/>
)
}
}
You missed your ()
<EnhancedTable onClick = {(e)=>{this.editUser(); this.deleteUser();}}/>
You are doing it right in
<EnhancedTable onClick = {(e)=>{this.editUser; this.deleteUser;}}/>
A minor change is needed:
<EnhancedTable onClick = {(e)=>{this.editUser(e); this.deleteUser(e);}}/>
A quick reference for what changed here:
let x = () => {
console.log('hello');
}
x; // This simply does nothing as it is just a reference to the function
x(); // This instead invokes the function

Change a react component's state on a click of another component

I am trying to show/hide a component based on its state and I want to change it on a click in a 3rd component.
//navbar
export class NavigationBar extends React.Component {
constructor(props) {
super(props);
this.state = {
showNotification: false,
}
}
handleNotification = () => this.setState({
showNotification: !this.state.showNotification,
});
{ this.state.showNotification ? <Outside><Notifications /></Outside> : null}
//outside component, responsible for detect if a click happened outside it.
export default class Outside extends Component {
constructor(props) {
super(props);
this.setWrapperRef = this.setWrapperRef.bind(this);
this.handleClickOutside = this.handleClickOutside.bind(this);
}
componentDidMount() {
document.addEventListener('mousedown', this.handleClickOutside)
}
setWrapperRef(node) {
this.wrapperRef = node;
}
handleClickOutside(event) {
if(this.wrapperRef && !this.wrapperRef.contains(event.target)) {
console.log("clicked outside notifications");
this.setState({
showNotification: false
})
}
}
render() {
return (
<div ref={this.setWrapperRef}>
{this.props.children}
</div>
)
}
}
Outside.propTypes = {
children: PropTypes.element.isRequired
}
My doubt is how can I change the state in navbar based on the event that is being detected inside Outside component ?
In parent, you need downward to Outside a event handler:
<Outside toggleNofitication={this.handleNotification}><Notifications /></Outside>
and in Outside, just call toggleNofitication when event fired:
handleClickOutside = () => {
// ...
this.props.toggleNofitication()
}

React reusing a component with different state

I'm pretty new to React and trying to write my first app to get a better understanding.
What I'm trying to build is a simple time tracking tool where the user can start and stop a work timer.
Here you can see the design I came up with:
If the user clicks on the "start" button the working time Timer component should update every second. If the user clicks then on the "take a break" button the timer should stop and instead the break time Timer component should start ticking.
I would like to reuse the Timer component for both working and break timer and just set different states.
I already managed to do this but I don't know if this is a nice way or if this can be improved and make it more generic?
My Tracker component looks like this:
class Tracker extends Component {
constructor(props) {
super(props);
this.state = {
workTime: 0,
breakTime: 0,
isRunning: false,
timerType: 'workTimer'
}
}
startTimer(type) {
this.setState({
isRunning: true,
timerType: type
});
this.timerInterval = setInterval(() => {
this.updateTimer()
}, 1000);
}
stopTimer() {
this.setState({
isRunning: false
});
clearInterval(this.timerInterval);
}
toggleBreak(type) {
this.setState({
timerType: type
});
if (!this.state.isRunning && this.state.timerType === 'breakTimer') {
this.startTimer('breakTimer');
} else if (this.state.isRunning && this.state.timerType === 'breakTimer') {
this.stopTimer();
this.startTimer('workTimer');
} else {
this.stopTimer();
this.startTimer('breakTimer');
}
}
updateTimer() {
let state = null;
if (this.state.timerType === 'workTimer') {
state = {
workTime: this.state.workTime + 1000
};
} else {
state = {
breakTime: this.state.breakTime + 1000
};
}
this.setState(state);
}
render() {
return (
<div className="tracker">
<Timer time={ this.state.workTime }/>
<Timer time={ this.state.breakTime }/>
<TimerControls
isRunning={ this.state.isRunning }
start={ () => this.startTimer('workTimer') }
stop={ () => this.stopTimer() }
toggleBreak={ () => this.toggleBreak('breakTimer') }
/>
</div>
);
}
}
Controls component:
class TimerControls extends Component {
constructor(props) {
super(props);
}
render() {
const {isRunning, start, stop, toggleBreak} = this.props;
return (
<div className="tracker__control">
<button onClick={ start } disabled={ isRunning }>Start</button>
<button onClick={ toggleBreak }>Break</button>
<button onClick={ stop } disabled={ !isRunning }>Stop</button>
</div>
);
}
}
Timer component:
class Timer extends Component {
constructor(props) {
super(props);
}
render() {
const { time } = this.props;
return (
<div className="tracker__timer">{ timeFormat(time) }</div>
);
}
}
Is there a way to get rid of the timerType conditions?

ES6 - Warning: setState(…): Cannot update during an existing state transition

I am rewriting some old ReactJS code, and got stuck fixing this error (the error repeats about 1700 times in the console, the DOM does not render at all):
Warning: setState(...): Cannot update during an existing state
transition (such as within render or another component's
constructor). Render methods should be a pure function of props and
state; constructor side-effects are an anti-pattern, but can be moved
to componentWillMount.
I am a Component that passes it's state down to a component that should render some controls. Based on the clicked controls, the state should change, and new controls should render.
So this is my Container component:
class TeaTimer extends Component {
constructor(props) {
super(props);
this.state = {
count: 120,
countdownStatus: 'started'
}
}
componentDidUpdate(prevProps, prevState) {
if (this.state.countdownStatus !== prevState.countdownStatus) {
switch (this.state.countdownStatus) {
case 'started':
this.startTimer();
break;
case 'stopped':
this.setState({count:0});
}
}
}
componentWillUnmount() {
clearInterval(this.timer);
delete this.timer;
}
startTimer() {
this.timer = setInterval(() => {
let newCount = this.state.count -1;
this.setState({
count: newCount >= 0 ? newCount : 0
});
if(newCount === 0) {
this.setState({countdownStatus: 'stopped'});
}
}, 1000)
}
handleStatusChange(newStatus) {
this.setState({ countdownStatus: newStatus });
}
render() {
let {count, countdownStatus} = this.state;
let renderStartStop = () => {
if (countdownStatus !== 'stopped') {
return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
} else {
return <div>This will be the slider form</div>
}
};
return(
<div className={styles.container}>
<p>This is the TeaTimer component</p>
<Clock totalSeconds={count}/>
{renderStartStop()}
</div>
)
}
}
And this is my controls component:
class StartStop extends Component {
constructor(props) {
super(props);
}
onStatusChange(newStatus) {
return() => {
this.props.onStatusChange(newStatus);
}
}
render() {
let {countdownStatus} = this.props;
let renderStartStopButton = () => {
if(countdownStatus === 'started') {
return <button onClick={()=> this.onStatusChange('stopped')}>Reset</button>;
} else {
return <button onClick={()=> this.onStatusChange('started')}>Start</button>
}
};
return(
<div className={styles.tt.Controls}>
{renderStartStopButton()}
</div>
)
}
}
StartStop.propTypes = {
countdownStatus: React.PropTypes.string.isRequired,
onStatusChange: React.PropTypes.func.isRequired
};
I am sorry about the wall of text, but I really can;t figure out where the error is coming from - and therefor don't know which part of the code I can leave out.
I have tried implementing the solution found in a seemingly related question, but can't get it to work either.
I think you have a typo in this line:
return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
It should be:
return <StartStop countdownStatus={countdownStatus} onStatusChange={() => this.handleStatusChange}/>
You seem to be calling the method handleStatusChange instead of passing it as a callback.
Your metods call each other so you must define two instance of your metods.
class StartStop extends Component {
constructor(props) {
super(props);
this.onStatusChangeReset=this.onStatusChange.bind(this);
this.onStatusChangeStart=this.onStatusChange.bind(this);
}
onStatusChange(newStatus) {
return() => {
this.props.onStatusChange(newStatus);
}
}
render() {
let {countdownStatus} = this.props;
let renderStartStopButton = () => {
if(countdownStatus === 'started') {
return <button onClick={this.onStatusChangeReset('stopped')}>Reset</button>;
} else {
return <button onClick={this.onStatusChangeStart('started')}>Start</button>
}
};
return(
<div className={styles.tt.Controls}>
{renderStartStopButton()}
</div>
)
}
}
StartStop.propTypes = {
countdownStatus: React.PropTypes.string.isRequired,
onStatusChange: React.PropTypes.func.isRequired
};
In this line in your return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/> gives the warning, the handleStatusChanged function is called on pressing a button which tries to change the state by setState keyword. whenever the state is changed render function is called again but in your case render function was in progress of returning while the render function is called again by setState keyword.

Setting state with onClick in render function

I have a button in render(), and I want it's onClick() to set the state. I know you shouldn't be setting the state in render() because it causes an infinite loop, so how should I go about this?
function initialState(props) {
return {
edit: false,
value: props.value,
};
}
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
}
onCancel() {
this.setState({ edit: false, value: this.props.value });
}
onClick() {
this.state.edit ? this.onCancel() : this.setState({ edit: true });
}
render() {
return (
<div onClick={this.onClick}>
BUTTON
</div>
);
}
Updated to show what the code I'm trying now and the warning I'm getting thousands of times:
Warning: setState(...): Cannot update during an existing state transition (such as
within `render` or another component's constructor). Render methods should be a
pure function of props and state; constructor side-effects are an anti-pattern, but
can be moved to `componentWillMount`.
warning # warning.js?0260:44
getInternalInstanceReadyForUpdate # ReactUpdateQueue.js?fd2c:51
enqueueSetState # ReactUpdateQueue.js?fd2c:192
ReactComponent.setState # ReactComponent.js?702a:67
onCancel # mybutton.js?9f63:94
onClick # mybutton.js?9f63:98
render # mybutton.js?
...
Not really sure what you want to do since the previous answers didn't solve the issue. So if you provide some more information it might get easier.
But here is my take on it:
getInitialState() {
return (
edit: true
);
}
handleEdit() {
this.setState({edit: true});
}
handelCancel() {
this.setState({edit: false});
}
render() {
var button = <button onClick={this.handelEdit}>Edit</button>
if(this.state.edit === true) {
button = <button onClick={this.handelCancel}>Cancel</button>
}
return (
<div>
{button}
</div>
);
}
To set the state for your use case you need to set the state somewhere but I wouldn't do it this way. I would bind a function to the onClick event.
function initialState(props) {
return {
edit: false,
value: props.value,
};
}
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
this.handleButtonClick = this.onClick.bind(this);
}
onCancel() {
this.setState({ edit: false, value: this.props.value });
}
onClick() {
this.state.edit ? this.onCancel() : this.setState({ edit: true });
}
render() {
return (
<div onClick={this.handleButtonClick}>
BUTTON
</div>
);
}
Look here for more information
Try to make use of arrow functions to bind onBtnClick and onCancel function to the context and see if it solves your problem.
function initialState(props) {
return {
edit: false,
value: props.value,
};
}
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
}
onCancel = ()=> {
this.setState({ edit: false, value: this.props.value });
}
onBtnClick = () => {
this.state.edit ? this.onCancel() : this.setState({ edit: true });
}
render() {
return (
<div onClick={this.onBtnClick}>
BUTTON
</div>
);
}

Categories

Resources