const { compose, withState, withHandlers, mapProps } = Recompose const { omit } = _ const forbidCharacters = forbiddenCharsRegexp => withHandlers({ onChange: props => event => { if (props.onChange) { const value = event.target.value const cleanValue = value.replace(forbiddenCharsRegexp, '') const newEvent = { ...event, target: { ...event.target, value: cleanValue } } props.onChange(newEvent) } } }) const formatInputValue = ({ formatValue, parseValue }) => compose( withState('inputValue', 'setInputValue', props => formatValue(props.value)), withHandlers({ onChange: props => event => { props.setInputValue(event.target.value) }, onBlur: props => event => { const parsedValue = parseValue(props.inputValue) const formattedValue = formatValue(parsedValue) props.setInputValue(formattedValue) const newEvent = { ...event, target: { ...event.target, value: parsedValue } } if (props.onChange) { props.onChange(newEvent) } if (props.onBlur) { props.onBlur(newEvent) } } }), mapProps(({ inputValue, ...otherProps }) => ({ ...omit(otherProps, ['inputValue', 'setInputValue']), value: inputValue })) ) const BaseInput = props => <input {...props} /> const PhoneNumberInput = compose( formatInputValue({ formatValue: value => { return value.replace(/^(\d{3})(\d{3})(\d{4})$/, '($1) $2-$3') }, parseValue: formattedValue => { return formattedValue.replace(/[^\d]/g, '').slice(0, 10) } }), forbidCharacters(/[^\d\s\-()]/g) )(BaseInput) class App extends React.Component { constructor(props) { super(props) this.state = { phoneNumber: '' } } onChange = event => { this.setState({ phoneNumber: event.target.value }) } render() { return ( <div> <PhoneNumberInput value={this.state.phoneNumber} onChange={this.onChange} style={{ padding: 5, fontSize: 'large' }} /> <br /> <code>{JSON.stringify(this.state)}</code> </div> ) } } ReactDOM.render(<App />, document.getElementById('container'))