TypeError: can't access property "toLocaleLowerCase", unitText is undefined
Reduced Test Case
Do you understand that if a reduced test case is not provided, we will intentionally delay triaging of your ticket?
- I understand
Which connector are you using (React/Angular/etc)?
No connector (vanilla JS)
Bug Description
TypeError in locale todayHint/prevHint/nextHint when a custom view's duration has no single-unit denominator (e.g. { month: 12 })
Description
When a custom view is configured with a duration that reduces to a non-unit value (e.g. a 12-month "year" view defined as duration: { month: 12 } instead of duration: { year: 1 }), clicking the today, prev, or next toolbar buttons while that view is active throws a TypeError inside locale hint functions, e.g.:
TypeError: can't access property "toLocaleLowerCase", unitText is undefined
at todayHint (locales/es.js:28)
at formatWithOrdinals (main.js:2755)
at buttonHint (main.js:7598)Reproduction
new Calendar(el, {
locale: 'es',
views: {
dayGridYear: {
type: 'dayGrid',
duration: { month: 12 }, // 12 !== 1, so it never resolves to a single unit
},
},
initialView: 'dayGridYear',
headerToolbar: { left: 'prev,today,next', center: 'title', right: 'dayGridMonth,dayGridYear' },
});Click Today (or Prev/Next) while dayGridYear is the active view → crash.
Root cause
In buildViewSpec(), viewSpec.singleUnit is only set when greatestDurationDenominator(duration).value === 1:
if (denom.value === 1) {
singleUnit = durationUnit;
...
}For duration: { month: 12 }, the denominator is { unit: 'month', value: 12 }, so singleUnit stays ''.
Later, in the toolbar button-building code (around main.js:7590-7607), the today/prev/next hint callback computes unitText as:
buttonHint = (currentUnit) => {
return formatWithOrdinals(buttonInput.hint || calendarOptions[name + 'Hint'], [
calendarOptions[currentUnit + 'TextLong'] ||
calendarOptions[currentUnit + 'Text'],
currentUnit
], buttonText);
};When currentUnit is '' (empty singleUnit), this evaluates to calendarOptions['TextLong'] || calendarOptions['Text'], both of which are undefined. So unitText is undefined when passed to the locale's todayHint/prevHint/nextHint.
Most locale packs (e.g. es, and likely others following the same pattern) assume unitText is always a string and call .toLocaleLowerCase() on it unconditionally:
// locales/es.js
todayHint(unitText, unit) {
return (unit === 'day') ? 'Hoy' :
((unit === 'week') ? 'Esta' : 'Este') + ' ' + unitText.toLocaleLowerCase();
},→ crash.
Notably, getButtonState() (used elsewhere, main.js:8684-8722) already guards against this exact case:
const currentUnit = currentData.viewSpec.singleUnit;
const currentHintOrdinal = [
currentUnit ? getSingleUnitText(currentUnit, options) : '',
currentUnit,
];but the toolbar-button-building code path (used when actually rendering the today/prev/next buttons, around main.js:7596-7607) does not have the same guard.
Suggested fix
Apply the same currentUnit ? ... : '' guard in the toolbar button hint callback, so unitText is always a string:
buttonHint = (currentUnit) => {
return formatWithOrdinals(buttonInput.hint || calendarOptions[name + 'Hint'], [
currentUnit
? (calendarOptions[currentUnit + 'TextLong'] || calendarOptions[currentUnit + 'Text'] || '')
: '',
currentUnit
], buttonText);
};This keeps behavior consistent with getButtonState()/getSingleUnitText() and prevents locale hint functions from ever receiving undefined as unitText.
Environment
fullcalendar(vanilla JS) v7.1.0- Reproduced with locale
es, but any locale whosetodayHint/prevHint/nextHintcalls a string method onunitTextunconditionally is affected.
Source: fullcalendar/fullcalendar