Skip to content

Commit

Permalink
Improved performance on rendering 'day' with highlighted days (#1180)
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-shamshurin authored and martijnrusschen committed Dec 16, 2017
1 parent 55f9bdb commit 87039ac
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 42 deletions.
7 changes: 5 additions & 2 deletions src/calendar.jsx
Expand Up @@ -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,
Expand Down Expand Up @@ -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})
Expand Down
34 changes: 34 additions & 0 deletions src/date_utils.js
Expand Up @@ -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
}
30 changes: 5 additions & 25 deletions src/day.jsx
Expand Up @@ -6,8 +6,6 @@ import {
getMonth,
getDate,
now,
isMoment,

isSameDay,
isDayDisabled,
isDayInRange,
Expand All @@ -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,
Expand Down Expand Up @@ -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 = () => {
Expand Down
15 changes: 10 additions & 5 deletions src/index.jsx
Expand Up @@ -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'

Expand Down Expand Up @@ -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 () {
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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}
Expand Down
2 changes: 1 addition & 1 deletion src/month.jsx
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/week.jsx
Expand Up @@ -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,
Expand Down
21 changes: 13 additions & 8 deletions 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(
Expand Down Expand Up @@ -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)
})

Expand All @@ -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)
})

Expand All @@ -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)
})

Expand All @@ -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)
})
})
})
Expand Down

0 comments on commit 87039ac

Please sign in to comment.