WIP Remove Timeouts to make OptIn simpler and more reliable

pull/10616/head
Jared Scheib 2017-08-11 17:57:33 -07:00
parent d59d3c8fcd
commit 751c008b90
2 changed files with 64 additions and 93 deletions

View File

@ -15,23 +15,27 @@ class ClickOutsideInput extends Component {
render() {
const {
id,
type,
customPlaceholder,
onGetRef,
customValue,
onFocus,
onChange,
onKeyPress,
onKeyDown,
} = this.props
return (
<input
className="form-control input-sm"
id={id}
type={type}
name={customPlaceholder}
ref={onGetRef}
value={customValue}
onFocus={onFocus}
onChange={onChange}
onKeyPress={onKeyPress}
onKeyDown={onKeyDown}
placeholder={customPlaceholder}
/>
)
@ -41,12 +45,14 @@ class ClickOutsideInput extends Component {
const {func, string} = PropTypes
ClickOutsideInput.propTypes = {
id: string.isRequired,
type: string.isRequired,
customPlaceholder: string.isRequired,
onGetRef: func.isRequired,
customValue: string.isRequired,
onGetRef: func.isRequired,
onFocus: func.isRequired,
onChange: func.isRequired,
onKeyPress: func.isRequired,
onKeyDown: func.isRequired,
handleClickOutsideCustomValueInput: func.isRequired,
}

View File

@ -1,12 +1,9 @@
import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import ClickOutsideInput from 'shared/components/ClickOutsideInput'
import uuid from 'node-uuid'
// these help ensure that blur and toggle events don't both change toggle's side
const RESET_TIMEOUT = 300
const TOGGLE_CLICKED_TIMEOUT = 20
const BLUR_FOCUS_GAP_TIMEOUT = 10
import ClickOutsideInput from 'shared/components/ClickOutsideInput'
class OptIn extends Component {
constructor(props) {
@ -18,58 +15,34 @@ class OptIn extends Component {
useCustomValue: customValue !== '',
fixedValue,
customValue,
wasToggleClicked: false,
wasCustomValueInputBlurred: false,
resetTimeoutID: null,
toggleTimeoutID: null,
blurTimeoutID: null,
}
this.id = uuid.v4()
this.isCustomValueInputFocused = false
this.useFixedValue = ::this.useFixedValue
this.toggleValue = ::this.toggleValue
this.useCustomValue = ::this.useCustomValue
this.handleClickFixedValueField = ::this.handleClickFixedValueField
this.handleClickToggle = ::this.handleClickToggle
this.handleFocusCustomValueInput = ::this.handleFocusCustomValueInput
this.handleBlurCustomValueInput = ::this.handleBlurCustomValueInput
// this.handleFocusCustomValueInput = ::this.handleFocusCustomValueInput
this.handleChangeCustomValue = ::this.handleChangeCustomValue
this.handleKeyPressCustomValueInput = ::this.handleKeyPressCustomValueInput
this.handleKeyDownCustomValueInput = ::this.handleKeyDownCustomValueInput
this.handleClickOutsideCustomValueInput = ::this
.handleClickOutsideCustomValueInput
this.handleGetRefCustomValueInput = ::this.handleGetRefCustomValueInput
this.considerResetCustomValue = ::this.considerResetCustomValue
this.setCustomValue = ::this.setCustomValue
this.setValue = ::this.setValue
}
componentWillUnmount() {
clearTimeout(this.state.resetTimeoutID)
clearTimeout(this.state.toggleTimeoutID)
clearTimeout(this.state.blurTimeoutID)
}
useFixedValue() {
this.setState({useCustomValue: false}, this.setValue)
}
toggleValue() {
const useCustomValueNext = !this.state.useCustomValue
if (useCustomValueNext && !this.state.wasCustomValueInputBlurred) {
this.useCustomValue()
} else {
this.useFixedValue()
}
this.setState({useCustomValue: false, customValue: ''}, () =>
this.setValue()
)
// this.customValueInput.blur()
}
useCustomValue() {
this.setState({useCustomValue: true}, () => {
if (
this.state.wasToggleClicked &&
!this.state.wasCustomValueInputBlurred
) {
this.customValueInput.focus()
}
this.setValue()
})
this.setState({useCustomValue: true}, () => this.setValue())
}
handleClickFixedValueField() {
@ -78,38 +51,20 @@ class OptIn extends Component {
handleClickToggle() {
return () => {
this.setState({wasToggleClicked: true}, () => {
this.toggleValue()
})
const toggleTimeoutID = setTimeout(() => {
this.setState({wasToggleClicked: false})
}, TOGGLE_CLICKED_TIMEOUT)
this.setState({toggleTimeoutID})
const useCustomValueNext = !this.state.useCustomValue
if (useCustomValueNext) {
this.useCustomValue()
this.customValueInput.focus()
} else {
this.useFixedValue()
}
}
}
handleFocusCustomValueInput() {
return () => this.useCustomValue()
}
handleBlurCustomValueInput() {
return e => {
this.setState(
{wasCustomValueInputBlurred: true, customValue: e.target.value.trim()},
() => {
if (this.state.customValue === '') {
const blurTimeoutID = setTimeout(() => {
if (!this.state.wasToggleClicked) {
this.useFixedValue()
}
}, BLUR_FOCUS_GAP_TIMEOUT)
this.setState({blurTimeoutID})
}
}
)
return () => {
this.isCustomValueInputFocused = true
this.useCustomValue()
}
}
@ -119,24 +74,40 @@ class OptIn extends Component {
}
}
handleKeyPressCustomValueInput() {
handleKeyDownCustomValueInput() {
return e => {
if (e.key === 'Enter') {
this.customValueInput.blur()
if (e.key === 'Enter' || e.key === 'Tab') {
if (e.key === 'Enter') {
this.customValueInput.blur()
}
this.considerResetCustomValue()
}
}
}
handleGetRefCustomValueInput() {
return el => (this.customValueInput = el)
}
handleClickOutsideCustomValueInput() {
return e => {
console.log(e)
if (
e.target.id !== this.grooveKnob.id &&
this.isCustomValueInputFocused
) {
this.considerResetCustomValue()
}
}
}
considerResetCustomValue() {
const customValue = this.customValueInput.value.trim()
this.setState({customValue})
if (customValue === '') {
this.useFixedValue()
}
this.isCustomValueInputFocused = false
}
setCustomValue(value) {
this.setState({customValue: value}, this.setValue)
}
@ -144,22 +115,12 @@ class OptIn extends Component {
setValue() {
const {onSetValue} = this.props
const {useCustomValue, fixedValue, customValue} = this.state
if (useCustomValue) {
onSetValue(customValue)
} else {
this.setState({customValue: ''})
onSetValue(fixedValue)
}
// reset UI interaction state-tracking values & prevent blur + click
const resetTimeoutID = setTimeout(() => {
this.setState({
wasToggleClicked: false,
wasCustomValueInputBlurred: false,
})
}, RESET_TIMEOUT)
this.setState({resetTimeoutID})
}
render() {
@ -173,17 +134,21 @@ class OptIn extends Component {
})}
>
<ClickOutsideInput
id={this.id}
type={type}
customPlaceholder={customPlaceholder}
customValue={customValue}
onGetRef={this.handleGetRefCustomValueInput()}
onGetRef={el => (this.customValueInput = el)}
onFocus={this.handleFocusCustomValueInput()}
onChange={this.handleChangeCustomValue()}
onKeyPress={this.handleKeyPressCustomValueInput()}
onKeyDown={this.handleKeyDownCustomValueInput()}
handleClickOutsideCustomValueInput={this.handleClickOutsideCustomValueInput()}
/>
<div
className="opt-in--groove-knob-container"
id={this.id}
ref={el => (this.grooveKnob = el)}
onClick={this.handleClickToggle()}
>
<div className="opt-in--groove-knob" />