I've got this simple v6 redux-form with an input that renders a custom component that populates its value using react-day-picker.
https://github.com/gpbl/react-day-picker
I chose react-day-picker over others because it doesn't depend on moment.js and works with my current set up.
When I focus the field, I want the datepicker to pop up, but if I click anywhere that's not the datepicker, I want it to disappear.
Essentially, I want my React datepicker to work like the jQueryUI one in:
https://jqueryui.com/datepicker/
The three scenarios I end up with are:
I click the field, the datepicker pops up, but will not disappear unless I select a date or click the field again (this is too rigid for our needs).
I click the field, the datepicker pops up, but will disappear TOO quickly if I click anywhere, as the input field's onBlur gets called before it processes the click event for the datepicker to populate the field with the chosen date.
I click the field, the datepicker pops up, gets auto-focused, blurs properly, except when I click anything that's not < body> or the datepicker.
I first tried to use a sibling empty div that wraps the whole page, so when I click the empty div, it'll toggle the datepicker properly. This worked OK with z-indexes and position: fixed until I changed the datepicker's month, which seems to re-render the datepicker and messed with the order of the clicking, which led to situation 2) again.
My most current attempt is to auto-focus the datepicker div when it pops up, so when I blur anything that's not the datepicker, it will toggle the datepicker. This worked in theory, except the datepicker is a component with many nested < div>'s inside it to control day, week, month, disabled days... so when I click a 'day', it registers a blur because I'm clicking the 'day' <div>
, not the root 'datepicker' <div>
, which is what was initially focused.
The solution to the above was to tweak 'datepicker' <div>
's onBlur such that it will only toggle the datepicker when document.activeElement is < body>, but that only works if I don't click another form field.
WizardFormPageOne.js:
function WizardFormPageOne({ handleSubmit }) {
return (
<form onSubmit={handleSubmit} className="col-xs-6">
<h1>WizardFormPageOne</h1>
<div className="card">
<div className="card-block">
<div className="form-group">
<label htmlFor="first">Label 1</label>
<Field type="text" name="first" component={DateInput} className="form-control" />
</div>
...
export default reduxForm({
form: 'wizardForm',
destroyOnUnmount: false,
})(WizardFormPageOne);
DateInput.js:
import React from 'react';
import styles from './styles.css';
import DatePicker from '../DatePicker';
class DateInput extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
dateValue: new Date(),
activeDateWidget: false,
};
}
changeDate = (date) => {
this.setState({
dateValue: date,
});
}
changeActiveDateWidget = (e) => {
e.stopPropagation();
this.setState({
activeDateWidget: !this.state.activeDateWidget,
});
}
render() {
const { input, meta } = this.props;
const { dateValue, activeDateWidget } = this.state;
return (
<div className={styles.dateInput}>
<input
{...input}
className="form-control"
type="text"
value={dateValue}
onClick={this.changeActiveDateWidget}
// onBlur={this.changeActiveDateWidget}
/>
{activeDateWidget ? (
<div>
<DatePicker
changeActiveDateWidget={this.changeActiveDateWidget}
changeDate={this.changeDate}
dateValue={dateValue}
/>
</div>
) : (
<div></div>
)}
</div>
);
}
}
export default DateInput;
DatePicker.js:
import React from 'react';
import 'react-day-picker/lib/style.css';
import DayPicker, { DateUtils } from 'react-day-picker';
import styles from './styles.css';
import disabledDays from './disabledDays';
class DatePicker extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
selectedDay: new Date(),
};
}
componentDidMount() {
if (this._input) {
this._input.focus();
}
}
handleDayClick = (e, day, { disabled }) => {
e.stopPropagation();
if (disabled) {
return;
}
this.setState({ selectedDay: day }, () => {
this.props.changeDate(day);
this.props.changeActiveDateWidget();
});
}
focusThisComponent = (e) => {
if (e) {
this._input = e;
}
}
focusIt = () => {
console.log('focusing');
}
blurIt = () => {
console.log('blurring');
setTimeout(() => {
if (document.activeElement == document.body) {
this.props.changeActiveDateWidget();
}
}, 1);
}
render() {
const { changeActiveDateWidget } = this.props;
const { selectedDay } = this.state;
return (
<div
className={styles.datePicker}
ref={this.focusThisComponent}
tabIndex="1"
onFocus={this.focusIt}
onBlur={this.blurIt}
>
<DayPicker
id="THISTHING"
initialMonth={selectedDay}
disabledDays={disabledDays}
selectedDays={(day) => DateUtils.isSameDay(selectedDay, day)}
onDayClick={this.handleDayClick}
/>
</div>
);
}
}
export default DatePicker;
Here's a screencast of the issue I'm having now:
http://screencast.com/t/kZuIwUzl
The datepicker toggles properly, except when clicking on another field, at which point it stops blurring/toggling properly. All my tinkering either led me make to one of the three scenarios listed above.
You can take this example http://react-day-picker.js.org/examples/?overlay and do some small modifications to make it redux-form v6 compatible. Instead of using local state you should use this.props.input.value
provided by redux-form Field
component inside your render function. Additionally in the handleInputChange
event handler you have to call this.props.input.onChange(e.target.value)
instead of this.setState({ value: e.target.value })
and in handleInputBlur
event call this.props.input.onBlur(e.target.value)
. That's all you have to do to make it work as a redux-form Field
component.