00. Challenge starting point
đ TL;DR
- This is real-life code I ended up with while prototyping a Calendar component
- There is unnecessary complexity in the
Calendar Day
styling - Letâs take a look at where weâre at đ
âŹď¸ Skip to the challenge
đ˘ Background
While prototyping the Calendar App, I spent a few days messing around with the Calendar
component.
The CalendarDay
component particulary suffered a bit of âcomplexity creepâ over time, as I was discovering new âedge casesâ and adding some conditional logic to handle styling.
I had good intentions. I started with a style lookup object, just like weâve done in the previous two challenges:
const baseClasses = 'relative w-12 max-w-full ...'
const dynamicClasses = {
disabled: 'text-slate-300 pointer-events-none',
candidate: 'hover:bg-slate-100 text-slate-900',
hasAvailability: 'bg-indigo-100 text-indigo-600 font-bold hover:bg-indigo-200',
today: 'text-indigo-600 font-bold hover:bg-slate-100',
selected: 'bg-indigo-600 text-white font-bold bg-stripes',
}
The keys in the dynamicClasses
object are what I determined would be the different statuses a calendar day could be in:
- disabled: the day is not available, because itâs in the past or in a future month
- candidate: the day is within the available range, but there is no booking availability on that day
- hasAvailability: the day has some booking availabilies
- today: the day is today!
- selected: the day is currently selected
Itâs a bit more complicated than that
While this list of statuses really helped me provide styles for each scenario, I progressively realised that there was a bit more to it, in terms of complexity.
For example, a calendar day could actually be in both today
and hasAvailability
statuses at the same time.
In that case, which styles should be applied?
I ended up having a progressively more and more confusing series of conditional checks to handle style precedence between each status.
And hereâs what my code ended up looking like:
<CalendarDay
{...props}
className={cx(
baseClasses,
isSelected && dynamicClasses.selected,
isDisabled && dynamicClasses.disabled,
isCurrentDay && !isSelected && dynamicClasses.today,
// Oh nooo... this is getting out of control!
hasAvailability && !isDisabled && !isSelected && dynamicClasses.hasAvailability,
!hasAvailability && !isCurrentDay && !isDisabled && !isSelected && dynamicClasses.candidate
)}
/>
I tried to revrite the conditional logic with nested ternary operators, but trust me: it didnât make it easier to reason about the logic.
You can see the starting point code in GitPod if you want.
This is not just me
Letâs take a look at another Calendar component example out there, and check how they handle dynamic styles for the individual days.
Hereâs one of the Calendar components from Tailwind UI:
// Sample from Tailwind UI
<button
className={cx(
'py-1.5 hover:bg-gray-100',
day.isCurrentMonth ? 'bg-white' : 'bg-gray-50',
(day.isSelected || day.isToday) && 'font-semibold',
day.isSelected && 'text-white',
!day.isSelected && day.isCurrentMonth && !day.isToday && 'text-gray-900',
!day.isSelected && !day.isCurrentMonth && !day.isToday && 'text-gray-400',
day.isToday && !day.isSelected && 'text-indigo-600',
// ...
)}
>
Hmmmmm. I see a similar pattern where each overlapping scenarios are handled one by one.
And there are a few other examples out there, which try to handle conditional logic inside the className
attribute.
There is nothing fundamentally wrong with handing all the conditions within the className
attribute, but I think there can be better ways to do it, at least in the context of our Calendar Day component.
And thatâs what weâre going to explore in this last workshop challenge!
đ Ready for some refactoring? đ
Of course you are!
Letâs get started!