React-dates: isDayHighlighted does not re-run on onChange with new startDate

Created on 27 Mar 2018  路  3Comments  路  Source: airbnb/react-dates

I am trying to highlight the 14 days after the selected start date because we do not allow rentals longer than 14 days. This is a good common use for something like highlighting. However, when onChange fires with a new startDate having been selected, isDayHighlighted is not re-run, so the highlighting is then incorrect.

onChange = ({ startDate, endDate }: any) => {
    if (startDate) {
      this.props.setPickupDate(startDate);
      this.setState({ startDate });
    }
    if (endDate) {
      this.props.setDropoffDate(endDate);
      this.setState({ endDate });
    } else {
      this.props.setDropoffDate(null);
      this.setState({ endDate: null });
    }
    if (startDate && endDate) {
      // run check availability
      const { pickupLocation } = this.props.activeBooking;
      const params = {
        location_ids: pickupLocation.multi_car_location_ids,
        pickup_on: moment(startDate).format('YYYY-MM-DD'),
        dropoff_on: moment(endDate).format('YYYY-MM-DD'),
      };
      this.props.locationsAvailability(params);
    }
  };

  onFocusChange = (focusedInput: any) =>
    this.setState({
      focusedInput: !focusedInput ? START_FOCUS : focusedInput,
    });

  isDayHighlighted =
    (day: any) => (
      isInclusivelyAfterDay(day, moment(this.state.startDate)) &&
      !isInclusivelyAfterDay(day, moment(this.state.startDate).add(14, 'days')) &&
      !this.state.endDate
    );

  isOutsideRange =
    (day: any) => (
      !isInclusivelyAfterDay(day, this.state.openDate) ||
      isInclusivelyAfterDay(day, moment(this.state.openDate).add(180, 'days'))
    );

  render() {
    return (
      <div className="sc-date-picker">
        <div>
          <DayPickerRangeController
            daySize={48}
            endDate={this.state.endDate}
            firstDayOfWeek={1}
            focusedInput={this.state.focusedInput}
            hideKeyboardShortcutsPanel
            initialVisibleMonth={() => moment(this.state.openDate)}
            isDayHighlighted={this.isDayHighlighted}
            isOutsideRange={this.isOutsideRange}
            minimumNights={0}
            numberOfMonths={this.state.numberOfMonths}
            onDatesChange={this.onChange}
            onFocusChange={this.onFocusChange}
            orientation="verticalScrollable"
            startDate={this.state.startDate}
          />
        </div>
      </div>
    );
  }
}

Most helpful comment

I think that isDayHighlighted (or any of the prop modifiers for that matter) only get recomputed if focus changes or if the method itself is different (see https://github.com/airbnb/react-dates/blob/master/src/components/DayPickerRangeController.jsx#L364-L400).

The motivation behind this was largely to try and minimize the number of times the prop modifiers are automatically recomputed because they're fairly expensive.

One thing I can recommend doing is using a factory method instead of isDayHighlighted directly. That's what we do internally. It allows to better control when you want to trigger a recalculation of the method (because you basically generate a new method every time you want to guarantee a recalculation).

For your case, I'd do something like the following:

constructor(props) {
  super(props);
  this.isDayHighlighted = this.isDayHighlightedFactory()
}

onChange = ({ startDate, endDate }: any) => {
    if (startDate) {
      this.props.setPickupDate(startDate);
      this.setState({ startDate }, () => {
        this.isDayHighlighted = this.isDayHighlightedFactory();
      });
    }
    ...
  };

  isDayHighlightedFactory() {
    return (day: any) => (
      isInclusivelyAfterDay(day, moment(this.state.startDate)) &&
      !isInclusivelyAfterDay(day, moment(this.state.startDate).add(14, 'days')) &&
      !this.state.endDate
    );
  }

  render() {
    return (
      <div className="sc-date-picker">
        <div>
          <DayPickerRangeController
            isDayHighlighted={this.isDayHighlighted}
            ...
          />
        </div>
      </div>
    );
  }
}

You can see that I generate a new isDayHighlighted method every time the start date changes (but only after I've updated the state). Does that make sense?

All 3 comments

+1

I think that isDayHighlighted (or any of the prop modifiers for that matter) only get recomputed if focus changes or if the method itself is different (see https://github.com/airbnb/react-dates/blob/master/src/components/DayPickerRangeController.jsx#L364-L400).

The motivation behind this was largely to try and minimize the number of times the prop modifiers are automatically recomputed because they're fairly expensive.

One thing I can recommend doing is using a factory method instead of isDayHighlighted directly. That's what we do internally. It allows to better control when you want to trigger a recalculation of the method (because you basically generate a new method every time you want to guarantee a recalculation).

For your case, I'd do something like the following:

constructor(props) {
  super(props);
  this.isDayHighlighted = this.isDayHighlightedFactory()
}

onChange = ({ startDate, endDate }: any) => {
    if (startDate) {
      this.props.setPickupDate(startDate);
      this.setState({ startDate }, () => {
        this.isDayHighlighted = this.isDayHighlightedFactory();
      });
    }
    ...
  };

  isDayHighlightedFactory() {
    return (day: any) => (
      isInclusivelyAfterDay(day, moment(this.state.startDate)) &&
      !isInclusivelyAfterDay(day, moment(this.state.startDate).add(14, 'days')) &&
      !this.state.endDate
    );
  }

  render() {
    return (
      <div className="sc-date-picker">
        <div>
          <DayPickerRangeController
            isDayHighlighted={this.isDayHighlighted}
            ...
          />
        </div>
      </div>
    );
  }
}

You can see that I generate a new isDayHighlighted method every time the start date changes (but only after I've updated the state). Does that make sense?

@majapw Amazing! Thank you! This was driving me crazy. This is a brilliant and elegant solution. Thank you so much. We'll be doing our best to contribute back to the library and that you for all your work.

Was this page helpful?
0 / 5 - 0 ratings