I’ve used to love ternaries back when in my high school days. It looked smart, concise, and elegant. Then in my college days, I started to use it less because how hard it is to skim when you need to understand it quick. When I started working professionally, like, actually exposed with more code, I started to hate it even more, just because how others abuse it to the point I don’t even want to use that operator at all.
I’m now treating ternary just like recursive: avoid it as best as you can, unless actually you need it.
Let’s talk about some of the common ternary pattern that I actually see in the wild.
Default Values
I see a lot of engineers that uses ternary when defining variables.
const roundedClass = rounded
? 'super-long tailwind-classes here'
: '';
I do think this is ugly. This can be refactored to this:
let roundedClass = '';
if (rounded) {
roundedClass = 'super-long tailwind-classes here';
}
You can just use the else
part of that ternary as default value to define the variable, and change it when it hit certain criteria. This way, you’re guaranteed to instantly know the default value without parsing the ternary in your head.
Also, if you’re using React, it’s much better if you use something like clsx
or classnames
if you use it for switching classes.
import { clsx } from 'clsx';
function Something({ rounded }) {
return (<div className={clsx({
'super-long tailwind-classes here': rounded
})}>
...
</div>)
}
Nested Ternary
This is the most annoying trait of ternary. I do despise this a lot, and I often ask the author to refactor this to normal if-else blocks.
import { useTranslation } from 'react-i18next';
function Example({ value }: { value: number | undefined }) {
const { t } = useTranslation();
const displayValue = value === undefined
? <LoadingComponent />
: value === 0
? t('none')
: value;
return (
<>
{displayValue}
</>
);
}
It’s hard to see or understand in one single skim.
import { useTranslation } from 'react-i18next';
function Example({ value }: { value: number | undefined }) {
const { t } = useTranslation();
let displayValue: React.ReactNode = <LoadingComponent />;
if (value === 0) {
displayValue = t('none');
} else if (value >= 1) {
displayValue = value;
}
return (
<>
{displayValue}
</>
);
}
This way, you instantly understand the branching, and the variable values.
String Manipulator
Most engineers know how dreadful PHP is, and I do think people that does this haven’t understand the fundamental why it is dreadful: mixing the logic and presentation.
When building string, it’s much preferable to see where this certain variable is placed and worry the content later. When using ternary in the template syntax, or just normal string appends, ternary breaks the reading flow.
Here’s a sample:
import React from 'react';
function MyComponent({ isActive }) {
return (
<div className={`button ${isActive ? 'bg-blue-500' : 'bg-gray-500'} text-white p-4 rounded`}>
Click me
</div>
);
}
export default MyComponent;
The sample is quite straightforward, if it is active, switch to blue, else, use gray. Easy peasy, but as you may know, the design will eventually evolve and a lot of engineers will just add extra class to it.
import React from 'react';
function MyComponent({ isActive }) {
return (
<div
className={`
button
${isActive ?
'bg-blue-500 shadow-lg cursor-pointer text-white hover:shadow-xl' :
'bg-gray-500 text-gray-400 cursor-default opacity-50'}
p-4 rounded
`}
>
Click me
</div>
);
}
export default MyComponent;
This is where the disaster struck. See how the line now have a lot of spaces and newline characters? This can just simply uses clsx
or classnames
to help switching classes. Much easier to read: what class shown at when.
import React from 'react';
import clsx from 'clsx';
function MyComponent({ isActive }) {
return (
<div
className={clsx('button p-4 rounded', {
// Active state
'bg-blue-500 shadow-lg cursor-pointer text-white hover:shadow-xl': isActive,
// Inactive state
'bg-gray-500 text-gray-400 cursor-default opacity-50': !isActive
} )}
>
Click me
</div>
);
}
export default MyComponent;
See now you can even add comment into it?
Another example, within a long string.
const emailContent = `
Hi ${userName},
${isModerator
? "This is just a personal message, not a ticket. Thank you for your moderation and continued support of the site!"
: "Thank you for being a website member! We appreciate your participation and engagement."}
Thank you for using our site.
${hasUnreadMessages ? "You have unread messages waiting for you." : "You have no unread messages."}
Your last login was on ${lastLoginDate.toLocaleDateString()}.
${new Date().getDate() === lastLoginDate.getDate() ? "Welcome back!" : "We missed you!"}
Best regards,
The Team
`;
When building template, you’re expected to just compose the string. Do not put any logic on the template. This is anti-pattern, in my opinion.
// Default message variables
let greetings = "Thank you for being a website member! We appreciate your participation and engagement.";
let thankYouMessage = "Thank you for using our site.";
let unreadMessages = "You have no unread messages.";
let loginMessage = `Your last login was on ${lastLoginDate.toLocaleDateString()}.`;
let welcomeBackMessage = "We missed you!";
// Override values based on conditions
if (isModerator) {
greetings = "This is just a personal message, not a ticket. Thank you for your moderation and continued support of the site!";
}
if (hasUnreadMessages) {
unreadMessages = "You have unread messages waiting for you.";
}
if (new Date().getDate() === lastLoginDate.getDate()) {
welcomeBackMessage = "Welcome back!";
}
// Compose the email content
const emailContent = `
Hi ${userName},
${greetings}
${thankYouMessage}
${unreadMessages}
${loginMessage}
${welcomeBackMessage}
Best regards,
The Team
`;
JSX Component Switch
Switching components in JSX. I see a lot of engineers actually do this in the JSX/TSX section of the code. Please don’t do this.
type ProgressBarProps = {
status: 'pulsing' | 'progressing' | 'disabled';
};
export function ProgressBar(props: ProgressBarProps) {
const { status } = props;
return (
<div>
{status === 'pulsing' ? (
<PulsingProgressBar />
) : status === 'progressing' ? (
<ProgressingProgressBar />
) : (
<DisabledProgressBar />
)}
</div>
);
}
This looked like there’s three ProgressBars that got rendered here. If there’s no props or even, they are all sharing the same props, just assign it to a variable first before rendering!
type ProgressBarProps = {
status: 'pulsing' | 'progressing' | 'disabled';
};
export function ProgressBar(props: ProgressBarProps) {
const { status } = props;
let Component;
if (status === 'pulsing') {
Component = PulsingProgressBar;
} else if (status === 'progressing') {
Component = ProgressingProgressBar;
} else {
Component = DisabledProgressBar;
}
return (
<div>
<Component />
</div>
);
}
This looks much cleaner since it looks like you’re just rendering one component.