From 87039acde1d78dc4db73074f22e380208eadb804 Mon Sep 17 00:00:00 2001 From: Alexander Shamshurin Date: Sat, 16 Dec 2017 20:03:48 +0400 Subject: [PATCH] Improved performance on rendering 'day' with highlighted days (#1180) --- src/calendar.jsx | 7 +++++-- src/date_utils.js | 34 ++++++++++++++++++++++++++++++++++ src/day.jsx | 30 +++++------------------------- src/index.jsx | 15 ++++++++++----- src/month.jsx | 2 +- src/week.jsx | 2 +- test/day_test.js | 21 +++++++++++++-------- 7 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/calendar.jsx b/src/calendar.jsx index 5ca718543..f26ed75c0 100644 --- a/src/calendar.jsx +++ b/src/calendar.jsx @@ -57,7 +57,7 @@ export default class Calendar extends React.Component { filterDate: PropTypes.func, fixedHeight: PropTypes.bool, formatWeekNumber: PropTypes.func, - highlightDates: PropTypes.array, + highlightDates: PropTypes.instanceOf(Map), includeDates: PropTypes.array, inline: PropTypes.bool, locale: PropTypes.string, @@ -114,7 +114,10 @@ export default class Calendar extends React.Component { } componentDidMount () { - /* monthContainer height is needed in time component to determine the height for the ul in the time component. setState here so height is given after final component layout is rendered */ + // monthContainer height is needed in time component + // to determine the height for the ul in the time component + // setState here so height is given after final component + // layout is rendered if (this.props.showTimeSelect) { this.assignMonthContainer = (() => { this.setState({monthContainer: this.monthContainer}) diff --git a/src/date_utils.js b/src/date_utils.js index 7c824cf2b..bc5481b92 100644 --- a/src/date_utils.js +++ b/src/date_utils.js @@ -376,3 +376,37 @@ export function getEffectiveMaxDate ({ maxDate, includeDates }) { return maxDate } } + +export function getHightLightDaysMap ( + highlightDates = [], + defaultClassName = 'react-datepicker__day--highlighted' +) { + const dateClasses = new Map() + for (let i = 0, len = highlightDates.length; i < len; i++) { + const obj = highlightDates[i] + if (isMoment(obj)) { + const key = obj.format('MM.DD.YYYY') + const classNamesArr = dateClasses.get(key) || [] + if (!classNamesArr.includes(defaultClassName)) { + classNamesArr.push(defaultClassName) + dateClasses.set(key, classNamesArr) + } + } else if (typeof obj === 'object') { + const keys = Object.keys(obj) + const className = keys[0] + const arrOfMoments = obj[keys[0]] + if (typeof className === 'string' && arrOfMoments.constructor === Array) { + for (let k = 0, len = arrOfMoments.length; k < len; k++) { + const key = arrOfMoments[k].format('MM.DD.YYYY') + const classNamesArr = dateClasses.get(key) || [] + if (!classNamesArr.includes(className)) { + classNamesArr.push(className) + dateClasses.set(key, classNamesArr) + } + } + } + } + } + + return dateClasses +} diff --git a/src/day.jsx b/src/day.jsx index 50b49a2f0..cadbbaa76 100644 --- a/src/day.jsx +++ b/src/day.jsx @@ -6,8 +6,6 @@ import { getMonth, getDate, now, - isMoment, - isSameDay, isDayDisabled, isDayInRange, @@ -19,7 +17,7 @@ export default class Day extends React.Component { day: PropTypes.object.isRequired, dayClassName: PropTypes.func, endDate: PropTypes.object, - highlightDates: PropTypes.array, + highlightDates: PropTypes.instanceOf(Map), inline: PropTypes.bool, month: PropTypes.number, onClick: PropTypes.func, @@ -56,30 +54,12 @@ export default class Day extends React.Component { const { day, highlightDates } = this.props if (!highlightDates) { - return {[defaultClassName]: false} - } - - const classNames = {} - for (let i = 0, len = highlightDates.length; i < len; i++) { - const obj = highlightDates[i] - if (isMoment(obj)) { - if (isSameDay(day, obj)) { - classNames[defaultClassName] = true - } - } else if (typeof obj === 'object') { - const keys = Object.keys(obj) - const arr = obj[keys[0]] - if (typeof keys[0] === 'string' && arr.constructor === Array) { - for (let k = 0, len = arr.length; k < len; k++) { - if (isSameDay(day, arr[k])) { - classNames[keys[0]] = true - } - } - } - } + return false } - return classNames + // Looking for className in the Map of {'day string, 'className'} + const dayStr = day.format('MM.DD.YYYY') + return highlightDates.get(dayStr) } isInRange = () => { diff --git a/src/index.jsx b/src/index.jsx index 230476a45..06ae89271 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -24,14 +24,14 @@ import { subtractMonths, subtractWeeks, subtractYears, - isSameDay, isDayDisabled, isDayInRange, getEffectiveMinDate, getEffectiveMaxDate, parseDate, - safeDateFormat + safeDateFormat, + getHightLightDaysMap } from './date_utils' import onClickOutside from 'react-onclickoutside' @@ -156,6 +156,9 @@ export default class DatePicker extends React.Component { if (this.props.inline && currentMonth !== nextMonth) { this.setPreSelection(nextProps.selected) } + if (this.props.highlightDates !== nextProps.highlightDates) { + this.setState({'highlightDates': getHightLightDaysMap(nextProps.highlightDates)}) + } } componentWillUnmount () { @@ -177,11 +180,13 @@ export default class DatePicker extends React.Component { minDate && isBefore(defaultPreSelection, minDate) ? minDate : maxDate && isAfter(defaultPreSelection, maxDate) ? maxDate : defaultPreSelection - return { open: this.props.startOpen || false, preventFocus: false, - preSelection: this.props.selected ? newDate(this.props.selected) : boundedPreSelection + preSelection: this.props.selected ? newDate(this.props.selected) : boundedPreSelection, + // transforming highlighted days (perhaps nested array) + // to flat Map for faster access in day.jsx + highlightDates: getHightLightDaysMap(this.props.highlightDates) } } @@ -424,7 +429,7 @@ export default class DatePicker extends React.Component { filterDate={this.props.filterDate} onClickOutside={this.handleCalendarClickOutside} formatWeekNumber={this.props.formatWeekNumber} - highlightDates={this.props.highlightDates} + highlightDates={this.state.highlightDates} includeDates={this.props.includeDates} inline={this.props.inline} peekNextMonth={this.props.peekNextMonth} diff --git a/src/month.jsx b/src/month.jsx index 3647a3593..170f87fd6 100644 --- a/src/month.jsx +++ b/src/month.jsx @@ -15,7 +15,7 @@ export default class Month extends React.Component { filterDate: PropTypes.func, fixedHeight: PropTypes.bool, formatWeekNumber: PropTypes.func, - highlightDates: PropTypes.array, + highlightDates: PropTypes.instanceOf(Map), includeDates: PropTypes.array, inline: PropTypes.bool, maxDate: PropTypes.object, diff --git a/src/week.jsx b/src/week.jsx index f92067aa0..67d78c4d2 100644 --- a/src/week.jsx +++ b/src/week.jsx @@ -12,7 +12,7 @@ export default class Week extends React.Component { excludeDates: PropTypes.array, filterDate: PropTypes.func, formatWeekNumber: PropTypes.func, - highlightDates: PropTypes.array, + highlightDates: PropTypes.instanceOf(Map), includeDates: PropTypes.array, inline: PropTypes.bool, maxDate: PropTypes.object, diff --git a/test/day_test.js b/test/day_test.js index a7a617e4b..ea946a8e6 100644 --- a/test/day_test.js +++ b/test/day_test.js @@ -1,7 +1,7 @@ import React from 'react' import Day from '../src/day' import { shallow } from 'enzyme' -import { getDayOfWeekCode, newDate, getDate, cloneDate, addDays, subtractDays, getMonth, newDateWithOffset } from '../src/date_utils' +import { getDayOfWeekCode, newDate, getDate, cloneDate, addDays, subtractDays, getMonth, newDateWithOffset, getHightLightDaysMap } from '../src/date_utils' function renderDay (day, props = {}) { return shallow( @@ -86,7 +86,8 @@ describe('Day', () => { const highlightDay1 = cloneDate(day) const highlightDay2 = addDays(cloneDate(day), 1) const highlightDates = [highlightDay1, highlightDay2] - const shallowDay = renderDay(day, { highlightDates }) + const highlightDatesMap = getHightLightDaysMap(highlightDates) + const shallowDay = renderDay(day, { highlightDates: highlightDatesMap }) expect(shallowDay.hasClass(className)).to.equal(true) }) @@ -95,18 +96,20 @@ describe('Day', () => { const highlightDay1 = subtractDays(cloneDate(day), 1) const highlightDay2 = addDays(cloneDate(day), 1) const highlightDates = [highlightDay1, highlightDay2] - const shallowDay = renderDay(day, { highlightDates }) + const highlightDatesMap = getHightLightDaysMap(highlightDates) + const shallowDay = renderDay(day, { highlightDates: highlightDatesMap }) expect(shallowDay.hasClass(className)).to.equal(false) }) - describe('highlighted prop is an array of objects with class name as a key and array of moments as a value', () => { + describe('prop is an array of objects with class name as a key and array of moments as a value', () => { it('should apply the highlighted class if in highlighted', () => { const day = newDate() const highlightDay1 = {'testClassName': [addDays(cloneDate(day), 1), cloneDate(day)]} const highlightDay2 = addDays(cloneDate(day), 2) const highlightDay3 = addDays(cloneDate(day), 3) const highlightDates = [highlightDay1, highlightDay2, highlightDay3] - const shallowDay = renderDay(day, { highlightDates }) + const highlightDatesMap = getHightLightDaysMap(highlightDates) + const shallowDay = renderDay(day, { highlightDates: highlightDatesMap }) expect(shallowDay.hasClass('testClassName')).to.equal(true) }) @@ -116,7 +119,8 @@ describe('Day', () => { const highlightDay2 = addDays(cloneDate(day), 3) const highlightDay3 = addDays(cloneDate(day), 4) const highlightDates = [highlightDay1, highlightDay2, highlightDay3] - const shallowDay = renderDay(day, { highlightDates }) + const highlightDatesMap = getHightLightDaysMap(highlightDates) + const shallowDay = renderDay(day, { highlightDates: highlightDatesMap }) expect(shallowDay.hasClass('testClassName')).to.equal(false) }) @@ -126,10 +130,11 @@ describe('Day', () => { const highlightDay2 = {'barClassName': [cloneDate(day)]} const highlightDay3 = cloneDate(day) const highlightDates = [highlightDay1, highlightDay2, highlightDay3] - const shallowDay = renderDay(day, { highlightDates }) + const highlightDatesMap = getHightLightDaysMap(highlightDates) + const shallowDay = renderDay(day, { highlightDates: highlightDatesMap }) expect(shallowDay.hasClass('fooClassName')).to.equal(true) expect(shallowDay.hasClass('barClassName')).to.equal(true) - expect(shallowDay.hasClass('react-datepicker__day--highlighted')).to.equal(true) + expect(shallowDay.hasClass(className)).to.equal(true) }) }) })