Categories
React Web Development

Final onDatesChange with React-Dates

React-Dates is a pretty awesome date-picker library for React built by the developers over at Air BNB. It works well and it’s fairly customizable, so I recommend you check it out if you’re looking for a React datepicker.

Our Desired Functionality:
One issue we encountered – or rather desired functionality that wasn’t baked in and was a little tricky to get just right – was what we called “Final onChange.” Basically, we wanted to fire our onChange event handler only when the user was done interacting with the calendar. This could be when the user finished selecting a range of dates, or when default dates existed and the user selected a new valid startDate before clicking away. This functionality had been requested on their GitHub issues page, but in the end it was determined the logic was more appropriate for the parent component. So we set about figuring out how to build it there.

Our Original Implementation:

class DateRangeInput extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            startDate: null,
            endDate: null,
            focusedInput: null,
        };

        this.valuesDirty = false;
    }

    handleDatesChange = ({ startDate, endDate }) => {
        if (!startDate.isSame(this.state.startDate)) {
            this.valuesDirty = true;
        }
        this.setState({ startDate, endDate });
        if (this.props.onChange && endDate && !endDate.isSame(this.state.endDate)) {
            this.props.onChange({ startDate, endDate });
        }
    };

    handleFocusChange = focusedInput => {
        this.setState({ focusedInput: focusedInput });
        if (!focusedInput && this.valuesDirty && this.props.onChange) {
            this.valuesDirty = false;
            this.props.onChange({ startDate: this.state.startDate, endDate: this.state.endDate });
        }
    };

    render() { /* DateRangePicker here */ }
}

Using momentjs’s isSame method, we would fire the onChange handler if the endDate had changed, or if the startDate had changed and the user navigated away from the calendar. This allowed us to fire onChange when the user had selected a start date and navigated away from the calendar. However it introduced a problem: any other activity resulted in two onChange invocations – one when the start date was selected, the second when the end date was selected.

Attempting to rectify this issue was a little tricky for a couple reasons. First, because handleDatesChange is actually invoked after handleFocusChange. Second, because attempting to access values from state (in this case, startDate and endDate) isn’t a good idea because as we all know: setState is an asynchronous call. Accessing values from state randomly could be subject to a race condition.

Our Solution:

// handle onChange here to ensure state values are current, as setState is asynchronous
afterStateChange = () => {
    let { startDate, endDate, focusedInput } = this.state;

    // fire onChange if either date has changed and calendar has closed (whether automatic or click away)
    if (!focusedInput && this.valuesDirty) {
        this.valuesDirty = false;
        this.props.onChange && this.props.onChange({ startDate: startDate, endDate: endDate });
    }
}

handleDatesChange = ({ startDate, endDate }) => {
    // check if either date has actually changed
    if (startDate && !startDate.isSame(this.state.startDate)) {
        this.valuesDirty = true;
    }
    if (endDate && !endDate.isSame(this.state.endDate)) {
        this.valuesDirty = true;
    }

    this.setState({ startDate, endDate }, this.afterStateChange);
};

handleFocusChange = focusedInput => {
    this.setState({ focusedInput: focusedInput }, this.afterStateChange);
};

What worked for us in the end was first to move all of our logic to a common afterStateChange handler. This ensured the state values we were using would be current. Next we added a valuesDirty check for the endDate (as we had for the startDate) to indicate whether either of the values had changed. And finally, whenever the state was changed, we’d fire our onChange handler only when two conditions were met: (1) when focusedInput was null (i.e. the user had finished selecting a date range or clicked away) and (2) when either of the dates had changed. Easy peasy – just not immediately intuitive!

Hope that helps anyone out there who’s using React Dates and is looking for that extra event!

Categories
Blog U.S. News & World Report Web Development

1472 Days

It’s been a while since I’ve posted anything. 1472 days, to be exact.

Gosh, I was just a newbie at U.S. News & World Report. So much has happened in that time…

Four years.
Three redesigns.
Two bosses.
One (old and outdated) WordPress site.

Oh, and I got married too. That’s kinda big, I’d be remiss if I didn’t mention that.

So my goal is to try and post something weekly. Probably on Fridays. Preferably related to software engineering. Ideally I’ll learn something along the way. And hopefully, someone else will too. That’s probably worth it, right?

As I’ve heard several people say (but most recently Gregg Hurwitz): “You can’t edit a blank page.” So here goes…