Skip to content

Instantly share code, notes, and snippets.

@mjasnikovs
Created September 30, 2019 03:46
Show Gist options
  • Save mjasnikovs/77a6a48323d14301865d8d94076b335e to your computer and use it in GitHub Desktop.
Save mjasnikovs/77a6a48323d14301865d8d94076b335e to your computer and use it in GitHub Desktop.
Input Guard for Apollo client local state management
import React, {Component} from 'react'
import PropTypes from 'prop-types'
const MAX_INT = 2147483647
const MIN_INT = -2147483648
const isNaturalNumber = input => {
return typeof input === 'number' && input % 1 === 0 && Number.isInteger(input) && input >= 0 && 1 / input !== -Infinity
}
const isInteger = input => {
if (!Number.isInteger(input)) {
return false
}
if (typeof input !== 'number' || input !== Math.floor(input) || input === Infinity || isNaN(input)) {
return false
}
if (input > MAX_INT || input < MIN_INT) {
return false
}
return true
}
const isFloat = input => {
if (typeof input !== 'number' || input !== parseFloat(input) || input === Infinity || isNaN(input)) {
return false
}
if (input > MAX_INT || input < MIN_INT) {
return false
}
return true
}
class InputGuard extends Component {
constructor (props) {
super(props)
this.state = {
currentValue: props.children.props.value,
isFocused: false
}
this.handleChange = this.handleChange.bind(this, props.children.props.onChange)
this.handleFocus = this.handleFocus.bind(this, props.children.props.onFocus)
this.handleBlur = this.handleBlur.bind(this, props.children.props.onBlur)
}
handleChange (onChange, e) {
if (this.props.type === 'string' || e.target.value === '') {
this.setState({currentValue: e.target.value})
if (typeof onChange === 'function') {
return onChange(e)
}
return null
} else if (this.props.type === 'int' && !isInteger(Number(e.target.value))) {
return null
} else if (this.props.type === 'float' && !isFloat(Number(e.target.value))) {
return null
} else if (this.props.type === 'naturalNumber' && !isNaturalNumber(Number(e.target.value))) {
return null
} else if (this.props.notZero === true && Number(e.target.value) === 0) {
return null
}
const event = {...e, ...{target: {...e.target, value: Number(e.target.value)}}}
this.setState({currentValue: event.target.value})
if (typeof onChange === 'function') {
return onChange(event)
}
}
handleFocus (onFocus, e) {
this.setState({isFocused: true})
if (typeof onFocus === 'function') {
onFocus(e)
}
}
handleBlur (onBlur, e) {
this.setState({isFocused: false})
if (typeof onBlur === 'function') {
onBlur(e)
}
}
static getDerivedStateFromProps (nextProps, prevState) {
if (nextProps.children.props.value !== prevState.currentValue && !prevState.isFocused) {
return {currentValue: nextProps.children.props.value}
}
return null
}
render () {
return React.cloneElement(this.props.children, {
value: this.state.currentValue,
onChange: this.handleChange,
onFocus: this.handleFocus,
onBlur: this.handleBlur
})
}
}
InputGuard.defaultProps = {
type: 'string',
notZero: false
}
InputGuard.propTypes = {
children: PropTypes.element.isRequired,
type: PropTypes.oneOf(['int', 'float', 'naturalNumber', 'string']),
notZero: PropTypes.bool
}
export default InputGuard
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment