Skip to content

Commit c95cbed

Browse files
author
Maja Wichrowska
committed
Rearchitecture of how modifiers are maintained and updated
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.
1 parent c4bc6d6 commit c95cbed

26 files changed

+3479
-533
lines changed

constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module.exports = {
22
DISPLAY_FORMAT: 'L',
33
ISO_FORMAT: 'YYYY-MM-DD',
4+
ISO_MONTH_FORMAT: 'YYYY-MM',
45

56
START_DATE: 'startDate',
67
END_DATE: 'endDate',

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@
103103
},
104104
"dependencies": {
105105
"airbnb-prop-types": "^2.4.1",
106-
"array-includes": "^3.0.2",
107106
"classnames": "^2.2.5",
108107
"consolidated-events": "^1.0.1",
109108
"lodash.throttle": "^4.1.1",

src/components/CalendarDay.jsx

+3-10
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const propTypes = forbidExtraProps({
1616
day: momentPropTypes.momentObj,
1717
daySize: nonNegativeInteger,
1818
isOutsideDay: PropTypes.bool,
19-
modifiers: PropTypes.object,
19+
modifiers: PropTypes.instanceOf(Set),
2020
isFocused: PropTypes.bool,
2121
tabIndex: PropTypes.oneOf([0, -1]),
2222
onDayClick: PropTypes.func,
@@ -32,7 +32,7 @@ const defaultProps = {
3232
day: moment(),
3333
daySize: DAY_SIZE,
3434
isOutsideDay: false,
35-
modifiers: {},
35+
modifiers: new Set(),
3636
isFocused: false,
3737
tabIndex: -1,
3838
onDayClick() {},
@@ -44,10 +44,6 @@ const defaultProps = {
4444
phrases: CalendarDayPhrases,
4545
};
4646

47-
export function getModifiersForDay(modifiers, day) {
48-
return day ? Object.keys(modifiers).filter(key => modifiers[key](day)) : [];
49-
}
50-
5147
export default class CalendarDay extends React.Component {
5248
shouldComponentUpdate(nextProps, nextState) {
5349
return shallowCompare(this, nextProps, nextState);
@@ -93,12 +89,9 @@ export default class CalendarDay extends React.Component {
9389

9490
if (!day) return <td />;
9591

96-
const modifiersForDay = getModifiersForDay(modifiers, day);
97-
9892
const className = cx('CalendarDay', {
9993
'CalendarDay--outside': isOutsideDay,
100-
}, modifiersForDay.map(mod => `CalendarDay--${mod}`));
101-
94+
}, Array.from(modifiers, mod => `CalendarDay--${mod}`));
10295

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

src/components/CalendarMonth.jsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import CalendarDay from './CalendarDay';
1515

1616
import getCalendarMonthWeeks from '../utils/getCalendarMonthWeeks';
1717
import isSameDay from '../utils/isSameDay';
18+
import toISODateString from '../utils/toISODateString';
1819

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

@@ -132,13 +133,13 @@ export default class CalendarMonth extends React.Component {
132133
isOutsideDay={!day || day.month() !== month.month()}
133134
tabIndex={isVisible && isSameDay(day, focusedDate) ? 0 : -1}
134135
isFocused={isFocused}
135-
modifiers={modifiers}
136136
key={dayOfWeek}
137137
onDayMouseEnter={onDayMouseEnter}
138138
onDayMouseLeave={onDayMouseLeave}
139139
onDayClick={onDayClick}
140140
renderDay={renderDay}
141141
phrases={phrases}
142+
modifiers={modifiers[toISODateString(day)]}
142143
/>
143144
))}
144145
</tr>

src/components/CalendarMonthGrid.jsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import CalendarMonth from './CalendarMonth';
1515
import isTransitionEndSupported from '../utils/isTransitionEndSupported';
1616
import getTransformStyles from '../utils/getTransformStyles';
1717
import getCalendarMonthWidth from '../utils/getCalendarMonthWidth';
18+
import toISOMonthString from '../utils/toISOMonthString';
19+
import isAfterDay from '../utils/isAfterDay';
1820

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

@@ -113,7 +115,7 @@ export default class CalendarMonthGrid extends React.Component {
113115
let newMonths = months;
114116

115117
if (hasMonthChanged && !hasNumberOfMonthsChanged) {
116-
if (initialMonth.isAfter(this.props.initialMonth)) {
118+
if (isAfterDay(initialMonth, this.props.initialMonth)) {
117119
newMonths = months.slice(1);
118120
newMonths.push(months[months.length - 1].clone().add(1, 'month'));
119121
} else {
@@ -208,13 +210,14 @@ export default class CalendarMonthGrid extends React.Component {
208210
{months.map((month, i) => {
209211
const isVisible =
210212
(i >= firstVisibleMonthIndex) && (i < firstVisibleMonthIndex + numberOfMonths);
213+
const monthString = toISOMonthString(month);
211214
return (
212215
<CalendarMonth
213-
key={month.format('YYYY-MM')}
216+
key={monthString}
214217
month={month}
215218
isVisible={isVisible}
216219
enableOutsideDays={enableOutsideDays}
217-
modifiers={modifiers}
220+
modifiers={modifiers[monthString]}
218221
monthFormat={monthFormat}
219222
orientation={orientation}
220223
onDayMouseEnter={onDayMouseEnter}

src/components/DayPicker.jsx

+18-25
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import getTransformStyles from '../utils/getTransformStyles';
2323
import getCalendarMonthWidth from '../utils/getCalendarMonthWidth';
2424
import isTouchDevice from '../utils/isTouchDevice';
2525
import getActiveElement from '../utils/getActiveElement';
26+
import isDayVisible from '../utils/isDayVisible';
2627

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

@@ -382,10 +383,6 @@ export default class DayPicker extends React.Component {
382383

383384
if (e) e.preventDefault();
384385

385-
if (this.props.onPrevMonthClick) {
386-
this.props.onPrevMonthClick(e);
387-
}
388-
389386
let translationValue =
390387
this.isVertical() ? this.getMonthHeightByIndex(0) : this.dayPickerWidth;
391388

@@ -414,10 +411,6 @@ export default class DayPicker extends React.Component {
414411

415412
if (e) e.preventDefault();
416413

417-
if (this.props.onNextMonthClick) {
418-
this.props.onNextMonthClick(e);
419-
}
420-
421414
let translationValue =
422415
this.isVertical() ? -this.getMonthHeightByIndex(1) : -this.dayPickerWidth;
423416

@@ -434,14 +427,14 @@ export default class DayPicker extends React.Component {
434427
}
435428

436429
getFocusedDay(newMonth) {
437-
const { getFirstFocusableDay } = this.props;
430+
const { getFirstFocusableDay, numberOfMonths } = this.props;
438431

439432
let focusedDate;
440433
if (getFirstFocusableDay) {
441434
focusedDate = getFirstFocusableDay(newMonth);
442435
}
443436

444-
if (newMonth && (!focusedDate || !this.isDayVisible(focusedDate, newMonth))) {
437+
if (newMonth && (!focusedDate || !isDayVisible(focusedDate, newMonth, numberOfMonths))) {
445438
focusedDate = newMonth.clone().startOf('month');
446439
}
447440

@@ -453,11 +446,13 @@ export default class DayPicker extends React.Component {
453446
}
454447

455448
maybeTransitionNextMonth(newFocusedDate) {
456-
const { focusedDate } = this.state;
449+
const { numberOfMonths } = this.props;
450+
const { currentMonth, focusedDate } = this.state;
457451

458452
const newFocusedDateMonth = newFocusedDate.month();
459453
const focusedDateMonth = focusedDate.month();
460-
if (newFocusedDateMonth !== focusedDateMonth && !this.isDayVisible(newFocusedDate)) {
454+
const isNewFocusedDateVisible = isDayVisible(newFocusedDate, currentMonth, numberOfMonths);
455+
if (newFocusedDateMonth !== focusedDateMonth && !isNewFocusedDateVisible) {
461456
this.onNextMonthClick(newFocusedDate);
462457
return true;
463458
}
@@ -466,11 +461,13 @@ export default class DayPicker extends React.Component {
466461
}
467462

468463
maybeTransitionPrevMonth(newFocusedDate) {
469-
const { focusedDate } = this.state;
464+
const { numberOfMonths } = this.props;
465+
const { currentMonth, focusedDate } = this.state;
470466

471467
const newFocusedDateMonth = newFocusedDate.month();
472468
const focusedDateMonth = focusedDate.month();
473-
if (newFocusedDateMonth !== focusedDateMonth && !this.isDayVisible(newFocusedDate)) {
469+
const isNewFocusedDateVisible = isDayVisible(newFocusedDate, currentMonth, numberOfMonths);
470+
if (newFocusedDateMonth !== focusedDateMonth && !isNewFocusedDateVisible) {
474471
this.onPrevMonthClick(newFocusedDate);
475472
return true;
476473
}
@@ -486,17 +483,6 @@ export default class DayPicker extends React.Component {
486483
});
487484
}
488485

489-
isDayVisible(day, newMonth) {
490-
const { numberOfMonths } = this.props;
491-
const { currentMonth } = this.state;
492-
493-
const month = newMonth || currentMonth;
494-
const firstDayOfFirstMonth = month.clone().startOf('month');
495-
const lastDayOfLastMonth = month.clone().add(numberOfMonths - 1, 'months').endOf('month');
496-
497-
return !day.isBefore(firstDayOfFirstMonth) && !day.isAfter(lastDayOfLastMonth);
498-
}
499-
500486
isHorizontal() {
501487
return this.props.orientation === HORIZONTAL_ORIENTATION;
502488
}
@@ -516,6 +502,11 @@ export default class DayPicker extends React.Component {
516502
}
517503

518504
updateStateAfterMonthTransition() {
505+
const {
506+
onPrevMonthClick,
507+
onNextMonthClick,
508+
} = this.props;
509+
519510
const {
520511
currentMonth,
521512
monthTransition,
@@ -528,8 +519,10 @@ export default class DayPicker extends React.Component {
528519

529520
const newMonth = currentMonth.clone();
530521
if (monthTransition === PREV_TRANSITION) {
522+
if (onPrevMonthClick) onPrevMonthClick();
531523
newMonth.subtract(1, 'month');
532524
} else if (monthTransition === NEXT_TRANSITION) {
525+
if (onNextMonthClick) onNextMonthClick();
533526
newMonth.add(1, 'month');
534527
}
535528

0 commit comments

Comments
 (0)