Avoid Ambiguous Booleans in Typescript
Recently, I’ve noticed a lot of boolean logic that may seem benign, but presents a lot of readability issues. This can be especially the case when designing React components.
Let’s evaluate the following React component and refactor the logic.
const MyComponentWithIcon = ({ text, icon }: { text?: string; icon?: boolean }) => {
return (
<div>
{icon === undefined || icon === true && (
<MyIcon />
)}
{text ?? 'Default Text'} //evaluates '' to ''
</div>
)
}
...
//usage
<MyComponentWithIcon icon text="override default" />
This is a simple component that displays an icon with text next to it. There’s also some default behaviors present, if you leave the props undefined. However, it’s not easy to tell what behavior icon has when you pass it into the component props.
Let’s refactor…
const MyComponentWithIcon = ({ text, showIcon = true }: { text?: string; showIcon?: boolean }) => {
return (
<div>
{!!showIcon && (
<MyIcon />
)}
{text ||'Default Text'} //evaluates '' to 'Default Text'
</div>
)
}
Avoid Complex Evaluations between Null vs. Undefined vs. False vs. True vs. <Value>
First we must get rid of the line icon === undefined || icon === true
. Practices like these lead to impossible to read codebases. The notion that a falsy (undefined
) and truthy evaluation should result in the icon showing is puzzling for a developer to interpret.
To fix, let’s first replace icon with a better propname, showIcon
. In general, naming booleans using the prefix show<Boolean>
or if<Boolean>
is great for redundancy in understanding the usage of the boolean.
Next, we want default behavior, which implies we can use ES6 syntax for default prop spread to indicate clearly that an icon should be displayed if showIcon
is not set, i.e. <MyComponentWithIcon />
and only hide if showIcon
overrides the default <MyComponentWithIcon showIcon=false/>
.
Finally, we can simplify the original line, and evaluate !!showIcon
to determine if the icon should show. A quick note here, using double bangs, or equivalentlyBoolean(showIcon)
, to convert an expression to a boolean
again presents helpful redundancy to a user.
Avoid Null-Coalescing in React Components
Finally, a matter of stylistic choice, but I generally do not recommend using null-coalescers in components (??). While the null-coalescing operator allows a string to evaluate to blank, this behavior you generally do not want. From a UI design perspective, you should omit elements, not allow for empty strings. Therefore, I highly recommend sticking to using || in your component code.
Philosophy
Booleans are binary and should be idempotently evaluated. You should always be able to reduce an evaluation that considers Null vs. Undefined vs. False vs. True vs. <Value> to False vs. True. To do so, I recommend starting by !! your final evaluation, the one that determines subsequent logic. This maintains idempotency because no matter how many times you subsequently call !! on your value, it must be evaluated and handled the same. In the end, handling boolean logic is a bit of an art, and sometimes the best solution might be one that breaks these rules.
P.S. Another Common Example
const x: { showIcon?: boolean } | undefined;
if (x?.showIcon !== false) { //show icon if x is undefined or if x.showIcon is undefined or x.showIcon === true
showIcon()
}
const x: { showIcon: boolean } = { ...assignX, showIcon: assignX?.showIcon || true }
//defaults usually help
if (!!x.showIcon) { //same logic
showIcon()
}
Not 100% the best design, but one idea to simplify. Also much easier to read conditionals that operate on the truthy value only.