Skip to content

Instantly share code, notes, and snippets.

@luggage66
Created February 8, 2017 15:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save luggage66/e30281df130ff734f8d27cb49ed086d6 to your computer and use it in GitHub Desktop.
Save luggage66/e30281df130ff734f8d27cb49ed086d6 to your computer and use it in GitHub Desktop.
A simple date input
import React, { Component } from 'react';
import moment from 'moment';
const DEFAULT_DISPLAY_FORMAT = 'MM/DD/YYYY'; // what the input shows when the user isn't typing
const DEFAULT_DATA_FORMAT = 'YYYY-MM-DD'; // the value is expected to be in this format and this control ouputs this format
const DEFAULT_ACCEPTED_FORMATS = [ //the user may type of of these
'MM/DD/YYYY',
'M/D/YY',
'M/D',
'MM-DD-YYYY',
'M-D-YY',
'M-D'
];
export default class DateInput extends Component
{
static propTypes = {
value: React.PropTypes.string,
onChange: React.PropTypes.func.isRequired,
onBlur: React.PropTypes.func,
dataFormat: React.PropTypes.string, //a moment.js format string
displayFormat: React.PropTypes.string, //a moment.js format string
acceptedFormats: React.PropTypes.oneOfType([ // moment.js format string, or an array of
React.PropTypes.string,
React.PropTypes.array
])
}
constructor(props) {
super(props);
this.state = {
value: this.formatValue(props.value)
};
//bindy bindy
[
'handleBlur',
'handleChange'
].forEach(fnName => this[fnName] = this[fnName].bind(this));
}
get dataFormat() {
return this.props.dataFormat || DEFAULT_DATA_FORMAT;
}
get displayFormat() {
return this.props.displayFormat || DEFAULT_DISPLAY_FORMAT;
}
get acceptedFormats() {
return this.props.acceptedFormats || DEFAULT_ACCEPTED_FORMATS;
}
// either fires onChange OR discards the current value.
fireChangeEvent(newValue) {
if (!this.props.onChange) return;
if (moment.isMoment(newValue)) { //we are a date
if (newValue.isValid() && !newValue.isSame(moment(this.props.value, this.dataFormat))) {
// if we are a valid date AND different, then inform our parent
this.props.onChange(newValue.format(this.dataFormat));
}
else {
// otherwise, just clean up.
this.discardValue();
}
}
else {
//we are null. tell our parent if they don't know.
if (newValue !== this.props.value) {
this.props.onChange(newValue);
}
}
}
discardValue() {
this.setState({ value: this.formatValue(this.props.value) });
}
handleBlur(event) {
const parsedDate = this.parseDate(event.target.value);
this.fireChangeEvent(parsedDate);
//when we leave, change the displayed value back to the external one
// this.setState({ value: this.props.value });
if (this.props.onBlur) {
return this.props.onBlur(event);
}
}
handleChange(event) {
this.setState({ value: event.target.value });
}
componentWillReceiveProps(nextProps) {
// if we get a NEW value from our parent, accept it.
if (nextProps.value !== this.props.value || document.activeElement !== this.refs.input) {
this.setState({ value: this.formatValue(nextProps.value) });
}
}
// takes a string or null (if a string, it is expected to be a valid date
// in the strict DEFAULT_DATA_FORMAT)
// outputs a string in the display format.
formatValue(value) {
if (!value) return '';
return moment(value, this.dataFormat).format(this.displayFormat);
}
//takes a string and returns null (e.g. for empty string) or a moment (that may be invalid!)
parseDate(input) {
if (!input) return null;
input = input.trim();
if (input.length === 0) return null;
return moment(input, this.acceptedFormats);
}
focus() {
this.refs.input.focus();
}
render() {
// eslint-disable-next-line no-unused-vars
const { onChange, onBlur, value, acceptedFormats, displayFormat, dataFormat, ...props } = this.props;
return <input
type="text"
{...props}
ref='input'
onBlur={this.handleBlur}
onChange={this.handleChange}
value={this.state.value}
/>;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment