Ah, JavaScript. The language that powers the web has its quirks. While it has evolved significantly over the years, there are still some practices that make me scratch my head. Let's dive into some of the coding patterns that truly grind my gears.
1. Overusing Ternary for Conditional Rendering in React
The ternary operator is concise and can be a delight to use, that's why many languages introduced it. However, in React, I've noticed a trend that goes against the grain of most programming style guides. Consider this code:
{contacts.length
? contacts.map(contact => (
<li key={contact.id}>
{contact.firstName} {contact.lastName}
</li>
))
: null}
There are 3 reasons I'm not too fond of this approach, even though some React devs endorse it:
- It hinges on JavaScript's truthy/falsy definitions.
- The alternative path is empty, making the ternary redundant.
- The purpose of a ternary is to grasp the logic quickly, but multi-line format makes this harder.
Instead, I'd advocate for a more direct approach:
{contacts?.length > 0 && contacts.map(contact => (
<li key={contact.id}>
{contact.firstName} {contact.lastName}
</li>
))}
I know that it might be a controversial one, as there are quite a few posts that advocate using ternary.
2. Not Using Optional Chaining
Thanks to @ingosteinke for pointing this out! For some weird reason though it is a TS feature 🤦
As you saw in the previous example, we were testing something like this:
contacts.length > 0
It might be a problem if contacts are null
for example. We can prevent it by writing:
contacts && contacts.length > 0
But this can be refactored with the optional chaining operator (?.), which checks if the object accessed or function called is null or undefined. If that's the case it returns undefined
instead of throwing an error. So this condition could be changed to:
contacts?.length > 0
Note: even though optional chaining works with
undefined
, it won't work with a non-declared root objects.
3. The Reliance on for
Loop for Array Iteration
For those who began their coding journey with C/C++, for
loops are foundational. But when it comes to iterating over arrays in JavaScript, they're often overkill. Take this real-world ES5 code:
var values = [];
var keys = Object.keys(elemObj);
for (var i = 0; i < keys.length; i++) {
var val = parseInt(elemObj[keys[i]], 10);
values.push(val);
}
This can be elegantly refactored to:
const values = Object.keys(elemObj).map((key) => parseInt(key, 10));
I would argue that for
loops are rarely needed in JavaScript applications.
4. Improper Iteration
A close cousin to the previous point, I've seen developers use forEach
for value transformation:
const newArr = []
arr.forEach((el) => newArr.push(sometransformation(el)));
For such tasks, map
is the better tool. Use forEach
when you are concentrated rather on side effects, not simple transformations.
5. Overlooking the "Standard Library"
It's essential to be familiar with the built-in methods of a language. As a JavaScript developer you should know methods for basic types - like array, string, date or object. For instance, checking if an array contains an element:
if(arr.indexOf(el) != -1) { … }
Could be simplified to, as of ES2015 if I'm not mistaken:
if(arr.includes(el)) { … }
And let's not forget about other array methods like some
and every
.
6. Neglecting String Interpolation
For many years we built our string that included variables like this:
var str = "Hi, my name is " + name + " and I'm a " + occupation + ".";
ES6 introduced a beautiful feature for constructing strings, called template literals:
const str = `Hi, my name is ${name} and I'm a ${occupation}.`;
Much better and cleaner. While most people don't write new code using outdated methods, there's truly no reason to retain old practices in any codebase. Especially when tools like ESLint or Prettier can rectify such issues with a single command.
7. Importing Bulky Libs to Hastily
If Lighthouse reports that you have a lot of unused JavaScript, you might want to address it. Some frontend developers add dependencies to a project as if there's no tomorrow. While sometimes it's the best approach, adding dependencies to a frontend comes with a cost, as this code eventually becomes part of a bundle sent to the client.
Two of the biggest offenders might be Moment and lodash.
Moment is hefty, weighing in at nearly 80kB gzipped! I understand that the Date type in JS leaves much to be desired, but is 80kB really necessary just to display a date?
Lodash, on the other hand, is often used to simplify a few tasks here and there, but it comes in at 24kB gzipped. I'd argue that lodash doesn't belong in most frontend apps. However, for heavy, business-oriented frontends and backend JS, it's a different story.
So, before adding a dependency to your projects, ask yourself if you truly need it and check how much a package weighs. If you would like to go through cleaning up process, I wrote an article on optimizing Next.js bundle size on my blog.
In Conclusion
In reflecting on the nuances of JavaScript, it's clear that certain aspects can sometimes... well, grind one's gears. While JavaScript has its quirks, being mindful of these pitfalls can lead to cleaner, more efficient code. As developers, it's our responsibility to think while we code and educate ourselves. Not only to copy and paste old code or press tab as Copilot suggestions pop up on the screen.
Top comments (2)
You can add optional chaining to the list of overlooked modern ES builtins and write
instead of
No more "boo-boo", better code!
You are right, I missed that. Edited the article to include that. Thanks!