Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WOM-5377][BpkComponentCalendar] Optimise date formatting for improved INP #3695

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions packages/bpk-component-calendar/src/BpkCalendarDate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export type Props = DefaultProps & {

type DefaultProps = {
className?: string | null;
isoLabel?: string;
isBlocked?: boolean;
isFocused?: boolean;
isKeyboardFocusable?: boolean;
Expand Down Expand Up @@ -185,6 +186,7 @@ class BpkCalendarDate extends PureComponent<Props> {
}

delete buttonProps.preventKeyboardFocus;
delete buttonProps.isoLabel;

return (
<button
Expand Down
38 changes: 28 additions & 10 deletions packages/bpk-component-calendar/src/BpkCalendarGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ import type { ElementType } from 'react';
import { Component } from 'react';

import { cssModules, isDeviceIos } from '../../bpk-react-utils';
import deferCallback from '../../bpk-react-utils/src/deferCallback';

import { addCalendarGridTransition } from './BpkCalendarGridTransition';
import BpkCalendarWeek from './BpkCalendarWeek';
import { CALENDAR_SELECTION_TYPE } from './custom-proptypes';
import {
addMonths,
formatIsoDate,
getCalendarMonthWeeks,
getCalendar,
getCalendarNoCustomLabel,
isSameMonth,
} from './date-utils';
import { memoize } from './utils';

import type { DateModifiers, SelectionConfiguration } from './custom-proptypes';

Expand Down Expand Up @@ -79,8 +79,14 @@ export type Props = DefaultProps & {
weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6;
};

export type DateProps = {
val: Date;
customLabel: string | Date;
isoLabel: string;
};

type State = {
calendarMonthWeeks: Date[][];
calendarMonthWeeks: DateProps[][];
};
/*
BpkCalendarGrid - the grid representing a whole month
Expand Down Expand Up @@ -110,25 +116,39 @@ class BpkCalendarGrid extends Component<Props, State> {
constructor(props: Props) {
super(props);

// We cache expensive calculations (and identities) in state
this.state = {
calendarMonthWeeks: getCalendarMonthWeeks(
// Do not run expensive date formatting in the constructor
calendarMonthWeeks: getCalendarNoCustomLabel(
props.month,
props.weekStartsOn,
),
};
}

componentDidMount(): void {
// Defer expensive date formatting until after render to improve INP.
deferCallback(() =>
this.setState({
gwright170 marked this conversation as resolved.
Show resolved Hide resolved
calendarMonthWeeks: getCalendar(
this.props.month,
this.props.weekStartsOn,
this.props.formatDateFull,
),
}),
);
}

UNSAFE_componentWillReceiveProps(nextProps: Props) {
// We cache expensive calculations (and identities) in state
if (
!isSameMonth(nextProps.month, this.props.month) ||
nextProps.weekStartsOn !== this.props.weekStartsOn
) {
this.setState({
calendarMonthWeeks: getCalendarMonthWeeks(
calendarMonthWeeks: getCalendar(
nextProps.month,
nextProps.weekStartsOn,
nextProps.formatDateFull,
),
});
}
Expand All @@ -142,7 +162,6 @@ class BpkCalendarGrid extends Component<Props, State> {
dateModifiers,
dateProps,
focusedDate,
formatDateFull,
ignoreOutsideDate,
isKeyboardFocusable,
markOutsideDays,
Expand All @@ -166,12 +185,11 @@ class BpkCalendarGrid extends Component<Props, State> {
<div>
{calendarMonthWeeks.map((dates) => (
<BpkCalendarWeek
key={formatIsoDate(dates[0])}
key={dates[0].isoLabel}
month={month}
dates={dates}
onDateClick={onDateClick}
onDateKeyDown={onDateKeyDown}
formatDateFull={memoize(formatDateFull)}
DateComponent={DateComponent}
dateModifiers={dateModifiers!}
preventKeyboardFocus={preventKeyboardFocus}
Expand Down
44 changes: 36 additions & 8 deletions packages/bpk-component-calendar/src/BpkCalendarWeek-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,42 @@ const initialProps: Props = {
DateComponent: DummyDateComponent,
dateModifiers: {},
dates: [
new Date(1980, 4, 31),
new Date(1980, 5, 1),
new Date(1980, 5, 2),
new Date(1980, 5, 3),
new Date(1980, 5, 4),
new Date(1980, 5, 5),
new Date(1980, 5, 6),
].map(startOfDay),
{
val: startOfDay(new Date(1980, 4, 31)),
customLabel: 'Wednesday, 31 April 1980',
isoLabel: '1980-04-31',
},
{
val: startOfDay(new Date(1980, 5, 1)),
customLabel: 'Thursday, 1 May 1980',
isoLabel: '1980-05-01',
},
{
val: startOfDay(new Date(1980, 5, 2)),
customLabel: 'Friday, 2 May 1980',
isoLabel: '1980-05-02',
},
{
val: startOfDay(new Date(1980, 5, 3)),
customLabel: 'Saturday, 3 May 1980',
isoLabel: '1980-05-03',
},
{
val: startOfDay(new Date(1980, 5, 4)),
customLabel: 'Sunday, 4 May 1980',
isoLabel: '1980-05-04',
},
{
val: startOfDay(new Date(1980, 5, 5)),
customLabel: 'Monday, 5 May 1980',
isoLabel: '1980-05-05',
},
{
val: startOfDay(new Date(1980, 5, 6)),
customLabel: 'Tuesday, 6 May 1980',
isoLabel: '1980-05-06',
},
],
formatDateFull: (d: Date) => d.toString(),
preventKeyboardFocus: false,
markToday: true,
Expand Down
48 changes: 21 additions & 27 deletions packages/bpk-component-calendar/src/BpkCalendarWeek.tsx
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes in this file cover simplification of date comparison. There were a few places we needlessly formatted dates for comparison. This change reduces INP by roughly 20%.

Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ import {
endOfMonth,
} from './date-utils';

import type { DateProps } from './BpkCalendarGrid';
import type {
DateModifiers,
SelectionConfiguration,
SelectionConfigurationSingle,
SelectionConfigurationRange,
} from './custom-proptypes';


import STYLES from './BpkCalendarWeek.module.scss';

const getClassName = cssModules(STYLES);
Expand Down Expand Up @@ -103,7 +103,6 @@ function getSelectedDate(
* Gets the correct selection type for the current date
* @param {Date} date the current date of the calendar
* @param {Object} selectionConfiguration the current selection configuration
* @param {Function} formatDateFull function to format dates
* @param {Date} month the current month of the calendar
* @param {Number} weekStartsOn index of the first day of the week
* @param {Boolean} ignoreOutsideDate ignore date outside current month
Expand All @@ -112,7 +111,6 @@ function getSelectedDate(
function getSelectionType(
date: Date,
selectionConfiguration: SelectionConfiguration,
formatDateFull: (d: Date) => Date | string,
month: Date,
weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6,
ignoreOutsideDate: boolean,
Expand All @@ -130,8 +128,7 @@ function getSelectionType(
if (
selectionConfiguration.type === CALENDAR_SELECTION_TYPE.single &&
selectionConfiguration.date &&
(selectionConfiguration.date === formatDateFull(date) ||
formatDateFull(selectionConfiguration.date) === formatDateFull(date))
isSameDay(date, selectionConfiguration.date)
) {
return SELECTION_TYPES.single;
}
Expand Down Expand Up @@ -171,10 +168,10 @@ function getSelectionType(
) {
return SELECTION_TYPES.middle;
}
if (startDate && formatDateFull(startDate) === formatDateFull(date)) {
if (sameStartDay) {
return SELECTION_TYPES.start;
}
if (endDate && formatDateFull(endDate) === formatDateFull(date)) {
if (sameEndDay) {
return SELECTION_TYPES.end;
}
}
Expand All @@ -196,11 +193,11 @@ const singleDateHandler = (props: Props, nextProps: Props) => {

if (
((nextSelectConfig.date &&
isSameWeek(nextSelectConfig.date, nextProps.dates[0], {
isSameWeek(nextSelectConfig.date, nextProps.dates[0].val, {
weekStartsOn: nextProps.weekStartsOn,
})) ||
(currentSelectConfig.date &&
isSameWeek(currentSelectConfig.date, props.dates[0], {
isSameWeek(currentSelectConfig.date, props.dates[0].val, {
weekStartsOn: props.weekStartsOn,
}))) &&
currentSelectConfig.date !== nextSelectConfig.date
Expand Down Expand Up @@ -237,8 +234,7 @@ const rangeDateHandler = (props: Props, nextProps: Props) => {
export type Props = DefaultProps & {
DateComponent: ElementType;
dateModifiers: DateModifiers;
dates: Date[];
formatDateFull: (date: Date) => Date | string;
dates: DateProps[];
preventKeyboardFocus: boolean;
markToday: boolean;
markOutsideDays: boolean;
Expand Down Expand Up @@ -283,7 +279,6 @@ class BpkCalendarWeek extends Component<Props> {
const shallowProps = [
'DateComponent',
'dateModifiers',
'formatDateFull',
'isKeyboardFocusable',
'markOutsideDays',
'markToday',
Expand All @@ -305,11 +300,11 @@ class BpkCalendarWeek extends Component<Props> {
// we'll render, component should update.
if (
((nextProps.focusedDate &&
isSameWeek(nextProps.focusedDate, nextProps.dates[0], {
isSameWeek(nextProps.focusedDate, nextProps.dates[0].val, {
weekStartsOn: nextProps.weekStartsOn,
})) ||
(this.props.focusedDate &&
isSameWeek(this.props.focusedDate, this.props.dates[0], {
isSameWeek(this.props.focusedDate, this.props.dates[0].val, {
weekStartsOn: this.props.weekStartsOn,
}))) &&
this.props.focusedDate !== nextProps.focusedDate
Expand Down Expand Up @@ -363,7 +358,6 @@ class BpkCalendarWeek extends Component<Props> {
dateModifiers,
dateProps,
focusedDate,
formatDateFull,
ignoreOutsideDate,
isKeyboardFocusable,
markOutsideDays,
Expand All @@ -380,7 +374,7 @@ class BpkCalendarWeek extends Component<Props> {

if (ignoreOutsideDate) {
const daysOutside = this.props.dates.map((date) =>
isSameMonth(date, month),
isSameMonth(date.val, month),
);

const shouldRender = daysOutside.reduce(or);
Expand All @@ -395,13 +389,12 @@ class BpkCalendarWeek extends Component<Props> {
{this.props.dates.map((date) => {
const isBlocked =
minDate && maxDate
? !isWithinRange(date, { start: minDate, end: maxDate })
? !isWithinRange(date.val, { start: minDate, end: maxDate })
: false;

const dateSelectionType = getSelectionType(
date,
date.val,
selectionConfiguration!,
formatDateFull,
month,
weekStartsOn,
ignoreOutsideDate!,
Expand All @@ -410,25 +403,26 @@ class BpkCalendarWeek extends Component<Props> {
return (
<DateContainer
className={cellClassName}
isEmptyCell={!isSameMonth(date, month) && ignoreOutsideDate!}
isEmptyCell={!isSameMonth(date.val, month) && ignoreOutsideDate!}
isBlocked={isBlocked}
key={date.getDate()}
key={date.val.getDate()}
selectionType={dateSelectionType}
>
<DateComponent
date={date}
date={date.val}
modifiers={dateModifiers}
aria-label={formatDateFull(date)}
aria-label={date.customLabel}
onClick={onDateClick}
onDateKeyDown={onDateKeyDown}
preventKeyboardFocus={preventKeyboardFocus}
isKeyboardFocusable={isKeyboardFocusable}
isFocused={focusedDate && isSameDay(date, focusedDate)}
isSelected={getSelectedDate(date, selectionConfiguration!)}
isFocused={focusedDate && isSameDay(date.val, focusedDate)}
isSelected={getSelectedDate(date.val, selectionConfiguration!)}
isBlocked={isBlocked}
isOutside={markOutsideDays && !isSameMonth(date, month)}
isToday={markToday && isToday(date)}
isOutside={markOutsideDays && !isSameMonth(date.val, month)}
isToday={markToday && isToday(date.val)}
selectionType={dateSelectionType}
isoLabel={date.isoLabel}
{...dateProps}
/>
</DateContainer>
Expand Down
2 changes: 1 addition & 1 deletion packages/bpk-component-calendar/src/composeCalendar.tsx
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consistent memoization of formatDateFull (no need for consumers to implement this now)

Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ const composeCalendar = (
DateComponent={CalendarDate}
dateModifiers={dateModifiers}
daysOfWeek={daysOfWeek}
formatDateFull={formatDateFull}
formatDateFull={memoize(formatDateFull)}
formatMonth={memoize(formatMonth)}
month={month}
onDateClick={onDateClick}
Expand Down
Loading
Loading