Skip to content

Commit

Permalink
Rearchitecture of how modifiers are maintained and updated
Browse files Browse the repository at this point in the history
Instead of passing down an object of modifier string => modifiers functions to each CalendarDay component and expecting the CalendarDay component to take care of updating itself, we now put the burden on the top of the tree instead of on the leaves. The previous model basically ended up meaning that whenever an interaction happened on an individual day, even a hover interaction, every single CalendarDay would have to recalculate its modifiers and rerender as a result. This was, as you can imagine, really god damn slow.

In this new model, the DayPickerRangeController maintains a map with the following structure:
```
{
  MONTH_ISO_1: {
    DAY_ISO_1: Set(['modifer_1', 'modifier_2', ...]),
    DAY_ISO_2: Set(['modifer_1', 'modifier_2', ...]),
    ...
  },
  ...
}
```
It passes this down the tree such that each `CalendarMonth` and each `CalendarDay` only gets the information that pertains to it. This means that the updating of these modifiers is also handled at the top-level and is done in the `componentWillReceiveProps`, `onDayMouseEnter`, and `onDayMouseLeave` methods of the `DayPickerRangeController`. Fortunately, this allows us to more finely tune which days get updated and speeds up the rerendering/updating process dramatically.
  • Loading branch information
Maja Wichrowska committed Apr 25, 2017
1 parent 3815516 commit 9220ddb
Show file tree
Hide file tree
Showing 24 changed files with 3,374 additions and 504 deletions.
1 change: 1 addition & 0 deletions constants.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
DISPLAY_FORMAT: 'L',
ISO_FORMAT: 'YYYY-MM-DD',
ISO_MONTH_FORMAT: 'YYYY-MM',

START_DATE: 'startDate',
END_DATE: 'endDate',
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@
},
"dependencies": {
"airbnb-prop-types": "^2.4.1",
"array-includes": "^3.0.2",
"classnames": "^2.2.5",
"consolidated-events": "^1.0.1",
"lodash.throttle": "^4.1.1",
Expand Down
13 changes: 3 additions & 10 deletions src/components/CalendarDay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const propTypes = forbidExtraProps({
day: momentPropTypes.momentObj,
daySize: nonNegativeInteger,
isOutsideDay: PropTypes.bool,
modifiers: PropTypes.object,
modifiers: PropTypes.instanceOf(Set),
isFocused: PropTypes.bool,
tabIndex: PropTypes.oneOf([0, -1]),
onDayClick: PropTypes.func,
Expand All @@ -32,7 +32,7 @@ const defaultProps = {
day: moment(),
daySize: DAY_SIZE,
isOutsideDay: false,
modifiers: {},
modifiers: new Set(),
isFocused: false,
tabIndex: -1,
onDayClick() {},
Expand All @@ -44,10 +44,6 @@ const defaultProps = {
phrases: CalendarDayPhrases,
};

export function getModifiersForDay(modifiers, day) {
return day ? Object.keys(modifiers).filter(key => modifiers[key](day)) : [];
}

export default class CalendarDay extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
Expand Down Expand Up @@ -93,12 +89,9 @@ export default class CalendarDay extends React.Component {

if (!day) return <td />;

const modifiersForDay = getModifiersForDay(modifiers, day);

const className = cx('CalendarDay', {
'CalendarDay--outside': isOutsideDay,
}, modifiersForDay.map(mod => `CalendarDay--${mod}`));

}, Array.from(modifiers, mod => `CalendarDay--${mod}`));

const formattedDate = `${day.format('dddd')}, ${day.format('LL')}`;

Expand Down
3 changes: 2 additions & 1 deletion src/components/CalendarMonth.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CalendarDay from './CalendarDay';

import getCalendarMonthWeeks from '../utils/getCalendarMonthWeeks';
import isSameDay from '../utils/isSameDay';
import toISODateString from '../utils/toISODateString';

import ScrollableOrientationShape from '../shapes/ScrollableOrientationShape';

Expand Down Expand Up @@ -129,13 +130,13 @@ export default class CalendarMonth extends React.Component {
isOutsideDay={!day || day.month() !== month.month()}
tabIndex={isVisible && isSameDay(day, focusedDate) ? 0 : -1}
isFocused={isFocused}
modifiers={modifiers}
key={dayOfWeek}
onDayMouseEnter={onDayMouseEnter}
onDayMouseLeave={onDayMouseLeave}
onDayClick={onDayClick}
renderDay={renderDay}
phrases={phrases}
modifiers={modifiers[toISODateString(day)]}
/>
))}
</tr>
Expand Down
9 changes: 6 additions & 3 deletions src/components/CalendarMonthGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import CalendarMonth from './CalendarMonth';
import isTransitionEndSupported from '../utils/isTransitionEndSupported';
import getTransformStyles from '../utils/getTransformStyles';
import getCalendarMonthWidth from '../utils/getCalendarMonthWidth';
import toISOMonthString from '../utils/toISOMonthString';
import isAfterDay from '../utils/isAfterDay';

import ScrollableOrientationShape from '../shapes/ScrollableOrientationShape';

Expand Down Expand Up @@ -111,7 +113,7 @@ export default class CalendarMonthGrid extends React.Component {
let newMonths = months;

if (hasMonthChanged && !hasNumberOfMonthsChanged) {
if (initialMonth.isAfter(this.props.initialMonth)) {
if (isAfterDay(initialMonth, this.props.initialMonth)) {
newMonths = months.slice(1);
newMonths.push(months[months.length - 1].clone().add(1, 'month'));
} else {
Expand Down Expand Up @@ -205,13 +207,14 @@ export default class CalendarMonthGrid extends React.Component {
{months.map((month, i) => {
const isVisible =
(i >= firstVisibleMonthIndex) && (i < firstVisibleMonthIndex + numberOfMonths);
const monthString = toISOMonthString(month);
return (
<CalendarMonth
key={month.format('YYYY-MM')}
key={monthString}
month={month}
isVisible={isVisible}
enableOutsideDays={enableOutsideDays}
modifiers={modifiers}
modifiers={modifiers[monthString]}
monthFormat={monthFormat}
orientation={orientation}
onDayMouseEnter={onDayMouseEnter}
Expand Down
28 changes: 11 additions & 17 deletions src/components/DayPicker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import getTransformStyles from '../utils/getTransformStyles';
import getCalendarMonthWidth from '../utils/getCalendarMonthWidth';
import isTouchDevice from '../utils/isTouchDevice';
import getActiveElement from '../utils/getActiveElement';
import isDayVisible from '../utils/isDayVisible';

import ScrollableOrientationShape from '../shapes/ScrollableOrientationShape';

Expand Down Expand Up @@ -406,14 +407,14 @@ export default class DayPicker extends React.Component {
}

getFocusedDay(newMonth) {
const { getFirstFocusableDay } = this.props;
const { getFirstFocusableDay, numberOfMonths } = this.props;

let focusedDate;
if (getFirstFocusableDay) {
focusedDate = getFirstFocusableDay(newMonth);
}

if (newMonth && (!focusedDate || !this.isDayVisible(focusedDate, newMonth))) {
if (newMonth && (!focusedDate || !isDayVisible(focusedDate, newMonth, numberOfMonths))) {
focusedDate = newMonth.clone().startOf('month');
}

Expand All @@ -425,11 +426,13 @@ export default class DayPicker extends React.Component {
}

maybeTransitionNextMonth(newFocusedDate) {
const { focusedDate } = this.state;
const { numberOfMonths } = this.props;
const { currentMonth, focusedDate } = this.state;

const newFocusedDateMonth = newFocusedDate.month();
const focusedDateMonth = focusedDate.month();
if (newFocusedDateMonth !== focusedDateMonth && !this.isDayVisible(newFocusedDate)) {
const isNewFocusedDateVisible = isDayVisible(newFocusedDate, currentMonth, numberOfMonths);
if (newFocusedDateMonth !== focusedDateMonth && !isNewFocusedDateVisible) {
this.onNextMonthClick(newFocusedDate);
return true;
}
Expand All @@ -438,11 +441,13 @@ export default class DayPicker extends React.Component {
}

maybeTransitionPrevMonth(newFocusedDate) {
const { focusedDate } = this.state;
const { numberOfMonths } = this.props;
const { currentMonth, focusedDate } = this.state;

const newFocusedDateMonth = newFocusedDate.month();
const focusedDateMonth = focusedDate.month();
if (newFocusedDateMonth !== focusedDateMonth && !this.isDayVisible(newFocusedDate)) {
const isNewFocusedDateVisible = isDayVisible(newFocusedDate, currentMonth, numberOfMonths);
if (newFocusedDateMonth !== focusedDateMonth && !isNewFocusedDateVisible) {
this.onPrevMonthClick(newFocusedDate);
return true;
}
Expand All @@ -458,17 +463,6 @@ export default class DayPicker extends React.Component {
});
}

isDayVisible(day, newMonth) {
const { numberOfMonths } = this.props;
const { currentMonth } = this.state;

const month = newMonth || currentMonth;
const firstDayOfFirstMonth = month.clone().startOf('month');
const lastDayOfLastMonth = month.clone().add(numberOfMonths - 1, 'months').endOf('month');

return !day.isBefore(firstDayOfFirstMonth) && !day.isAfter(lastDayOfLastMonth);
}

isHorizontal() {
return this.props.orientation === HORIZONTAL_ORIENTATION;
}
Expand Down
Loading

0 comments on commit 9220ddb

Please sign in to comment.