Skip to content

Commit 3308e05

Browse files
authored
chore(performance): memoize calendar and reuse ISO date ids in day rendering (#2850)
* CalendarDay: Introduce isoDate/dateMonthId Signed-off-by: gpbl <io@gpbl.dev> * Memoize calendar values Signed-off-by: gpbl <io@gpbl.dev> * Lint file Signed-off-by: gpbl <io@gpbl.dev> * Use day.displayMonthId Signed-off-by: gpbl <io@gpbl.dev> --------- Signed-off-by: gpbl <io@gpbl.dev>
1 parent 1dececb commit 3308e05

File tree

3 files changed

+93
-26
lines changed

3 files changed

+93
-26
lines changed

src/DayPicker.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -699,19 +699,17 @@ export function DayPicker(initialProps: DayPickerProps) {
699699
return (
700700
// biome-ignore lint/a11y/useSemanticElements: react component
701701
<components.Day
702-
key={`${dateLib.format(date, "yyyy-MM-dd")}_${dateLib.format(day.displayMonth, "yyyy-MM")}`}
702+
key={`${day.isoDate}_${day.displayMonthId}`}
703703
day={day}
704704
modifiers={modifiers}
705705
className={className.join(" ")}
706706
style={style}
707707
role="gridcell"
708708
aria-selected={modifiers.selected || undefined}
709709
aria-label={ariaLabel}
710-
data-day={dateLib.format(date, "yyyy-MM-dd")}
710+
data-day={day.isoDate}
711711
data-month={
712-
day.outside
713-
? dateLib.format(date, "yyyy-MM")
714-
: undefined
712+
day.outside ? day.dateMonthId : undefined
715713
}
716714
data-selected={modifiers.selected || undefined}
717715
data-disabled={modifiers.disabled || undefined}

src/classes/CalendarDay.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export class CalendarDay {
1919
displayMonth && !dateLib.isSameMonth(date, displayMonth),
2020
);
2121
this.dateLib = dateLib;
22+
this.isoDate = dateLib.format(date, "yyyy-MM-dd");
23+
this.displayMonthId = dateLib.format(displayMonth, "yyyy-MM");
24+
this.dateMonthId = dateLib.format(date, "yyyy-MM");
2225
}
2326

2427
/**
@@ -48,6 +51,27 @@ export class CalendarDay {
4851
/** The date represented by this day. */
4952
readonly date: Date;
5053

54+
/**
55+
* Stable `yyyy-MM-dd` representation for reuse in keys/data attrs.
56+
*
57+
* @since V9.11.2
58+
*/
59+
readonly isoDate: string;
60+
61+
/**
62+
* Stable `yyyy-MM` representation of the displayed month.
63+
*
64+
* @since V9.11.2
65+
*/
66+
readonly displayMonthId: string;
67+
68+
/**
69+
* Stable `yyyy-MM` representation of the date's actual month.
70+
*
71+
* @since V9.11.2
72+
*/
73+
readonly dateMonthId: string;
74+
5175
/**
5276
* Checks if this day is equal to another `CalendarDay`, considering both the
5377
* date and the displayed month.

src/useCalendar.ts

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect } from "react";
1+
import { useEffect, useMemo } from "react";
22

33
import type {
44
CalendarDay,
@@ -84,6 +84,8 @@ export function useCalendar(
8484
| "fixedWeeks"
8585
| "ISOWeek"
8686
| "numberOfMonths"
87+
| "pagedNavigation"
88+
| "reverseMonths"
8789
| "disableNavigation"
8890
| "onMonthChange"
8991
| "month"
@@ -115,27 +117,70 @@ export function useCalendar(
115117
}, [props.timeZone]);
116118

117119
/** The months displayed in the calendar. */
118-
const displayMonths = getDisplayMonths(firstMonth, navEnd, props, dateLib);
119-
120-
/** The dates displayed in the calendar. */
121-
const dates = getDates(
122-
displayMonths,
123-
props.endMonth ? endOfMonth(props.endMonth) : undefined,
124-
props,
120+
// biome-ignore lint/correctness/useExhaustiveDependencies: We want to recompute only when specific props change.
121+
const { months, weeks, days, previousMonth, nextMonth } = useMemo(() => {
122+
const displayMonths = getDisplayMonths(
123+
firstMonth,
124+
navEnd,
125+
{ numberOfMonths: props.numberOfMonths },
126+
dateLib,
127+
);
128+
129+
const dates = getDates(
130+
displayMonths,
131+
props.endMonth ? endOfMonth(props.endMonth) : undefined,
132+
{
133+
ISOWeek: props.ISOWeek,
134+
fixedWeeks: props.fixedWeeks,
135+
broadcastCalendar: props.broadcastCalendar,
136+
},
137+
dateLib,
138+
);
139+
140+
const months = getMonths(
141+
displayMonths,
142+
dates,
143+
{
144+
broadcastCalendar: props.broadcastCalendar,
145+
fixedWeeks: props.fixedWeeks,
146+
ISOWeek: props.ISOWeek,
147+
reverseMonths: props.reverseMonths,
148+
},
149+
dateLib,
150+
);
151+
152+
const weeks = getWeeks(months);
153+
const days = getDays(months);
154+
155+
const previousMonth = getPreviousMonth(
156+
firstMonth,
157+
navStart,
158+
props,
159+
dateLib,
160+
);
161+
const nextMonth = getNextMonth(firstMonth, navEnd, props, dateLib);
162+
163+
return {
164+
months,
165+
weeks,
166+
days,
167+
previousMonth,
168+
nextMonth,
169+
};
170+
}, [
125171
dateLib,
126-
);
127-
128-
/** The Months displayed in the calendar. */
129-
const months = getMonths(displayMonths, dates, props, dateLib);
130-
131-
/** The Weeks displayed in the calendar. */
132-
const weeks = getWeeks(months);
133-
134-
/** The Days displayed in the calendar. */
135-
const days = getDays(months);
136-
137-
const previousMonth = getPreviousMonth(firstMonth, navStart, props, dateLib);
138-
const nextMonth = getNextMonth(firstMonth, navEnd, props, dateLib);
172+
firstMonth.getTime(),
173+
navEnd?.getTime(),
174+
navStart?.getTime(),
175+
props.disableNavigation,
176+
props.broadcastCalendar,
177+
props.endMonth?.getTime(),
178+
props.fixedWeeks,
179+
props.ISOWeek,
180+
props.numberOfMonths,
181+
props.pagedNavigation,
182+
props.reverseMonths,
183+
]);
139184

140185
const { disableNavigation, onMonthChange } = props;
141186

0 commit comments

Comments
 (0)