Skip to content

Instantly share code, notes, and snippets.

@zeroasterisk
Created September 29, 2016 05:00
Show Gist options
  • Save zeroasterisk/40830883564c2822bff98cd616964841 to your computer and use it in GitHub Desktop.
Save zeroasterisk/40830883564c2822bff98cd616964841 to your computer and use it in GitHub Desktop.
react-dates & uniforms = start/stop date range picker glory
import React from 'react';
import { DateRangePicker } from 'react-dates';
import momentPropTypes from 'react-moment-proptypes';
class DateRangePickerWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
focusedInput: null,
startDate: props.startDate || null,
endDate: props.startDate || null,
};
this.onDatesChange = this.onDatesChange.bind(this);
this.onFocusChange = this.onFocusChange.bind(this);
}
componentWillReceiveProps(nextProps) {
const { startDate, endDate } = nextProps;
if (startDate && endDate) {
this.setState({ startDate, endDate });
}
}
onDatesChange({ startDate, endDate }) {
this.props.handleDatesChange({ startDate, endDate });
this.setState({ startDate, endDate });
}
onFocusChange(focusedInput) {
this.setState({ focusedInput });
}
render() {
const { focusedInput, startDate, endDate } = this.state;
return (
<div>
<DateRangePicker
{...this.props}
onDatesChange={this.onDatesChange}
onFocusChange={this.onFocusChange}
focusedInput={focusedInput}
startDate={startDate}
endDate={endDate}
/>
</div>
);
}
}
const datePropTypes = React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.instanceOf(Date),
momentPropTypes.momentObj,
]);
DateRangePickerWrapper.propTypes = {
startDate: datePropTypes,
endDate: datePropTypes,
handleDatesChange: React.PropTypes.func.isRequired,
};
export default DateRangePickerWrapper;
/**
* Date Picker for Start, Stop dates
*
* schema fields (default): start, date, stats.durationDays
*
* Uses:
* https://github.com/vazco/uniforms
* https://github.com/airbnb/react-dates
*
* NOTE:
* For simple "external" state I created a non-uniforms wrapper
* ./DateRangePickerWrapper.js
*
* This allows the DateRangePickerWrapper to keep it's moment objects in
* DateRangePickerWrapper state as well as it's inputFocus.
*
* DateRangePickerWrapper will trigger handleDatesChange from this component.
*
* NOTE:
* I have a durationDays field, but if you don't have or want it, remove all
* instances an it would disappear. If you do not remove it, uniforms will
* complain that the field isn't found in the schema.
*/
import _ from 'lodash';
import React from 'react';
import classnames from 'classnames';
import moment from 'moment-timezone';
import { BaseField } from 'uniforms';
import DateRangePickerWrapper from './DateRangePickerWrapper';
// Date cleanup and handlers from uniforms DateInput
const makeDate = str => {
if (!str) return null;
if (_.isDate(str) || _.isString(str) || _.isNumber(str)) {
return moment(str).tz(moment.tz.guess()).toDate();
}
// console.error('makeDate invalid input', str);
return null;
};
// Here's the actual Uniforms enabled field
// NOTE this is using direct context vs. connectField()
// could possibly be refactored...
const DateStartStopInput = (props, { uniforms: { model, onChange } }) => {
const {
required,
startField,
endField,
durationDaysField,
tzField,
} = props;
const handleDatesChange = ({ startDate, endDate }) => {
// console.log('handleDatesChange', startDate, endDate);
if (!(startDate && endDate)) return null;
// calculate duration
const durationDays = moment(endDate).diff(startDate, 'days');
// save into Uniforms
onChange(startField, startDate.toDate());
onChange(endField, endDate.toDate());
onChange(durationDaysField, durationDays);
return null;
};
// get dates from model (from uniforms) as momentObjects
const startDate = moment(_.result(model, startField));
const endDate = moment(_.result(model, endField));
const durationDays = Math.max(1, _.result(model, durationDaysField));
// calculate timezone
const tz = moment(startDate).tz(moment.tz.guess()).format('z');
return (
<div className={classnames(
'form-group row',
{ required }
)}>
<label className="control-label col-sm-3">
Flight Dates
</label>
<div className="col-sm-9">
<div className="pull-sm-left m-r-1">
<DateRangePickerWrapper
{...props}
handleDatesChange={handleDatesChange}
startDate={startDate}
endDate={endDate}
/>
</div>
<div className="pull-sm-left small">
<div title="timezone" className="text-muted">
<small>
{tz}
</small>
</div>
<div title="duration" className="text-muted">
<small>
{durationDays} days
</small>
</div>
</div>
</div>
</div>
);
};
DateStartStopInput.propTypes = {
orderLine: React.PropTypes.object,
startField: React.PropTypes.string,
endField: React.PropTypes.string,
durationDaysField: React.PropTypes.string,
tzField: React.PropTypes.string,
required: React.PropTypes.bool,
start: React.PropTypes.instanceOf(Date),
};
DateStartStopInput.defaultProps = {
startField: 'start',
endField: 'stop',
durationDaysField: 'stats.durationDays',
tzField: 'tz',
};
DateStartStopInput.contextTypes = BaseField.contextTypes;
export default DateStartStopInput;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment