-
Notifications
You must be signed in to change notification settings - Fork 188
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
base: main
Are you sure you want to change the base?
Conversation
- reduce usage of date formats
Visit https://backpack.github.io/storybook-prs/3695 to see this build running in a browser. |
Visit https://backpack.github.io/storybook-prs/3695 to see this build running in a browser. |
Visit https://backpack.github.io/storybook-prs/3695 to see this build running in a browser. |
Browser supportIf this is a visual change, make sure you've tested it in multiple browsers. |
Visit https://backpack.github.io/storybook-prs/3695 to see this build running in a browser. |
Visit https://backpack.github.io/storybook-prs/3695 to see this build running in a browser. |
1 similar comment
Visit https://backpack.github.io/storybook-prs/3695 to see this build running in a browser. |
@@ -185,6 +186,7 @@ class BpkCalendarDate extends PureComponent<Props> { | |||
} | |||
|
|||
delete buttonProps.preventKeyboardFocus; | |||
delete buttonProps.formattedDate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensures prop isn't passed to html button, invalidating existing snapshots
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain a little more what is going on here, I'm not sure I understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The returned component includes {...buttonProps}
, so the html includes formattedDate="2024-12-19"
. This isn't an issue but:
- wasn't intentional
- invalidates all the existing snapshot tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, button props is derived from spreading the rest of this.props
. Where does this component end up getting used? I can't find where we end up needing the new formattedDate
prop, but I've not got a good picture of the Component hierarchy here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my case it is used in the Flights Search Controls GC to prevent re-generating iso date labels - https://github.skyscannertools.net/skyscanner/global-components/pull/1607/files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still don't understand, the prop is passed into the component, it's not referenced anywhere nor is this.props
passed into anything then its deleted from buttonProps
before it's used. How does including this prop in this component do anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies. The Global Component uses a custom Date component:
composeCalendar(null, BpkCalendarGridHeader, BpkScrollableCalendarGridList, Date).
We pass formattedDate
to the Date component. In the case of the GC this value is valuable, we use it to prevent duplicate generation of iso formatted dates (these dates are used to colour code dates with prices).
In the case of the default Backpack Date component that formattedDate value is not useful, we delete it from the props to avoid it appearing in the HTML and snapshots.
componentDidMount(): void { | ||
deferCallback(() => | ||
this.setState({ | ||
calendarMonthWeeks: getCalendarMonthWeeks( | ||
this.props.month, | ||
this.props.weekStartsOn, | ||
this.props.formatDateFull, | ||
), | ||
}), | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the constructor we get dates and formatted labels for everything but the aria-label. Here we regenerate dates, this time with aria labels, and defer until the thread is idle (typically after render). This change reduces INP by roughly 20%.
There was a problem hiding this comment.
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%.
There was a problem hiding this comment.
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)
val: currDate, | ||
textLabel: formatDate ? formatDate(currDate) : 'loading...', | ||
isoLabel: formatIsoDate(currDate), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perform all date formatting in a single place, rather than on demand across many files. This decreases the number of date format calls requires and contributes to a 20% reduction in INP.
const element = document.createElement('div'); | ||
element.scrollIntoView = jest.fn(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test was broken but was not being run because the test pattern specifies -test
rather than .test
. Renamed and fixed.
*/ | ||
|
||
/** | ||
* Mock deferCallback to immediately invoke the callback. This ensures that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See JSdoc
*/ | ||
|
||
/** | ||
* Defer a task until the browser is idle, or till the next "tick" if the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See JSdoc
registerRequireContextHook(); | ||
|
||
jest.mock('../../packages/bpk-react-utils/src/deferCallback'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Globally mock deferCallback to ensure calendar snapshots include real aria label on initial render, not 'loading...' fallback.
@@ -185,6 +186,7 @@ class BpkCalendarDate extends PureComponent<Props> { | |||
} | |||
|
|||
delete buttonProps.preventKeyboardFocus; | |||
delete buttonProps.formattedDate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain a little more what is going on here, I'm not sure I understand.
function getCalendarNoCustomLabel(date: Date, weekStartsOn: number) { | ||
return getCalendarMonthWeeks(date, weekStartsOn); | ||
} | ||
|
||
function getCalendar( | ||
date: Date, | ||
weekStartsOn: number, | ||
formatDate: (d: Date) => Date | string, | ||
) { | ||
return getCalendarMonthWeeks(date, weekStartsOn, formatDate); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Appreciate this could be cleaner but:
- I wanted to avoid separating the values into new state to avoid more refactoring and complexity to rationalise back together
- Mapping the values in after the initial generation is arguably slower and harder to read
Visit https://backpack.github.io/storybook-prs/3695 to see this build running in a browser. |
Visit https://backpack.github.io/storybook-prs/3695 to see this build running in a browser. |
Visit https://backpack.github.io/storybook-prs/3695 to see this build running in a browser. |
Visit https://backpack.github.io/storybook-prs/3695 to see this build running in a browser. |
Address performance issues when instantiating
BpkComponentCalendar
related to excessive date formatting (many times for 13 months worth of individual days). For Skyscanner staff please refer to https://skyscanner.atlassian.net/wiki/x/9eCWSw for complete analysis of the changes:formatDateFull
incomposeCalendar
Note
Deferring generation of date aria-labels is a workaround for a poorly performing date format function from
saddlebag-localisation
that Skyscanner are mandated to use - see test-localisation-perf. This solution isn't inherently bad (it's a method used by React Fibre for prioritising render) but adds complexity and indicates wider performance issues we should aim to resolve.Remember to include the following changes:
[KOA-123][BpkButton] Updating the colour
README.md
(If you have created a new component)README.md