javascript

This new React library will make you dump Redux forever

The new Zustand library changes everything for state management in web dev.

The simplicity completely blows Redux away. It’s like Assembly vs Python.

Forget action types, dispatch, Providers and all that verbose garbage.

Just use a hook! 👇

JavaScript
import { create } from 'zustand'; const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), }));

Effortless and intuitive with all the benefits of Redux and Flux — immutability, data-UI decoupling…

With none of the boilerplate — it’s just an object.

Redux had to be patched with hook support but Zustand was built from the ground up with hooks in mind.

JavaScript
function App() { const store = useStore(); return ( <div> <div>Count: {store.count}</div> <button onClick={store.increment}>Increment</button> </div> ); } export default App;

Share the store across multiple components and select only what you want:

JavaScript
function Counter() { // ✅ Only `count` const count = useStore((state) => state.count); return <div>Count: {count}</div>; } function Controls() { // ✅ Only `increment` const increment = useStore((state) => state.increment); return <button onClick={increment}>Increment</button>; }

Create multiple stores to decentralize data and scale intuitively.

Let’s be real, that single-state stuff doesn’t always make sense. And it defies encapsulation.

It’s often more natural to let a branch of components have their localized global state.

JavaScript
// ✅ More global store to handle the count data const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })); // ✅ More local store to handle user input logic const useControlStore = create((set) => ({ input: '', setInput: () => set((state) => ({ input: state.input })), })); function Controls() { return ( <div> <CountInput /> <Button /> </div> ); } function Button() { const increment = useStore((state) => state.increment); const input = useControlStore((state) => state.input); return ( <button onClick={() => increment(Number(input))}> Increment by {input} </button> ); } function CountInput() { const input = useControlStore((state) => state.input); return <input value={input} />; }

Meet useShallow(), a powerful way to get derived states — instantly updates when any of original states change.

JavaScript
import { create } from 'zustand'; import { useShallow } from 'zustand/react/shallow'; const useLibraryStore = create((set) => ({ fiction: 0, nonFiction: 0, borrowedBooks: {}, // ... })); // ✅ Object pick const { fiction, nonFiction } = useLibraryStore( useShallow((state) => ({ fiction: state.fiction, nonFiction: state.nonFiction, })) ); // ✅ Array pick const [fiction, nonFiction] = useLibraryStore( useShallow((state) => [state.fiction, state.nonFiction]) ); // ✅ Mapped picks const borrowedBooks = useLibraryStore( useShallow((state) => Object.keys(state.borrowedBooks)) );

And what if you don’t want instant updates — only at certain times?

It’s easier than ever — just pass a second argument to your store hook.

JavaScript
const user = useUserStore( (state) => state.user, (oldUser, newUser) => compare(oldUser.id, newUser.id) );

And how about derived updates based on previous states, like in React’s useState?

Don’t worry! In Zustand states update partially by default:

JavaScript
const useStore = create((set) => ({ user: { username: 'tariibaba', site: 'codingbeautydev.com', color: 'blue💙', }, premium: false, // `user` object is not affected // `state` is the curr state before the update unsubscribe: () => set((state) => ({ premium: false })), }));

It only works at the first level though — you have to handle deeper partial updates by yourself:

JavaScript
const useStore = create((set) => ({ user: { username: 'tariibaba', site: 'codingbeautydev.com', color: 'blue💙', }, premium: false, updateUsername: (username) => // 👇 deep updates necessary to retain other object properties set((state) => ({ user: { ...state.user, username } })), }));

If you don’t want it just pass the object directly with true as the two arguments.

JavaScript
const useStore = create((set) => ({ user: { username: 'tariibaba', site: 'codingbeautydev.com', color: 'blue💙', }, premium: false, // resetAccount: () => set({}, true), }));

Zustand even has built-in support for async actions — no need for Redux Thunk or any external library.

JavaScript
const useStore = create((set) => ({ user: { username: 'tariibaba', site: 'codingbeautydev.com', color: 'blue💙', }, premium: false, // ✅ async actions updateFavColor: async (color) => { await fetch('https://api.tariibaba.com', { method: 'PUT', body: color, }); set((state) => ({ user: { ...state.user, color } })); }, }));

It’s also easy to get state within actions, thanks to get — the 2nd param in create()‘s callback:

JavaScript
// ✅ `get` lets us use state directly in actions const useStore = create((set, get) => ({ user: { username: 'tariibaba', site: 'codingbeautydev.com', color: 'blue💙', }, messages: [], sendMessage: ({ message, to }) => { const newMessage = { message, to, // ✅ `get` gives us `user` object from: get().user.username, }; set((state) => ({ messages: [...state.messages, newMessage], })); }, }));

It’s all about hooks in Zustand, but if you want you can read and subscribe to values in state directly.

JavaScript
// Get a non-observed state with getState() const count = useStore.getState().count; useStore.subscribe((state) => { console.log(`new value: ${state.count}`); });

This makes it great for cases where the property changes a lot but you only need the latest value for intermediate logic, not direct UI:

JavaScript
export default function App() { const widthRef = useRef(useStore.getState().windowWidth); useEffect(() => { useStore.subscribe((state) => { widthRef.current = state.windowWidth; }); }, []); useEffect(() => { setInterval(() => { console.log(`Width is now: ${widthRef.current}`); }, 1000); }, []); // ... }

Zustand outshines Redux and Mobx and all the others in almost every way. Use it for your next project and you won’t regret it.

The secret code Google uses to monitor everything you do online

Google now has at least 3 ways to track your search clicks and visits that they hide from you.

Have you ever tried to copy a URL directly from Google Search?

When I did that a few months ago, I unexpectedly got something like this from my clipboard.

Plain text
https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwjUmK2Tk-eCAxXtV0EAHX3jCyoQFnoECAkQAQ&url=https%3A%2F%2Fcodingbeautydev.com%2Fblog%2Fvscode-tips-tricks&usg=AOvVaw0xw4tT2wWNUxkHWf90XadI&opi=89978449

I curiously visited the page and guess what? It took me straight to the original URL.

This cryptic URL turned out to be a middleman that would redirect you to the actual page.

But what for?

After some investigation, I discovered that this was how Google Search had been recording our clicks and tracking every single visited page.

They set custom data- attributes and a mousedown event on each link in the search results page:

HTML
<a jsname="UWckNb" href="https://codingbeautydev.com/blog/vscode-tips-tricks" data-jsarwt="1" data-usg="AOvVaw0xw4tT2wWNUxkHWf90XadI" data-ved="2ahUKEwjUmK2Tk-eCAxXtV0EAHX3jCyoQFnoECAkQAQ"> </a>

A JavaScript function would change the href to a new URL with several parameters including the original URL, as soon as you start clicking on it.

JavaScript
import express from 'express'; const app = express(); app.get('/url', (req, res) => { // Record click and stuff... res.redirect(req.query); }); app.listen(3000);

So even though the browser would show the actual URL at the bottom-left on hover, once you clicked on it to copy, the href would change instantly.

Why mousedown over click? Probably because there won’t be a click event when users open the link in a new tab, which is something that happens quite often.

And so after right-clicking to copy like I did, mousedown would fire and the href would change, which would even update that preview URL at the bottom-left.

The new www.google.com/url page would log the visit and move out of the way so fast you’d barely notice it — unless your internet moves at snail speed.

They use this data for tools like Google Analytics and Search Console so site owners can improve the quality of their search results and pages by analyzing click-rate — something probably also using as a Search ranking factor. Not to mention recording clicks on Search ads to rake all the billions of yearly ad revenue.

Google Search Console. Source: Search Console Discover report now includes Chrome data

But Google got smarter.

They realized this URL tracking method had a serious issue for a certain group. For their users with slower internet speeds, the annoying redirect technique added a non-trivial amount of delay to the request and increased bounce rate.

So they did something new.

Now, instead of that cryptic www.google.com/url stuff, you get… the same exact URL?

With the <a> ping attribute, they have now successfully moved their tracking behind the scenes.

The ping attribute specifies one or more URLs that will be notified when the user visits the link. When a user opens the link, the browser asynchronously sends a short HTTP POST request to the URLs in ping.

The keyword here is asynchronously — www.google.com/url quietly records the click in the background without ever notifying the user, avoiding the redirect and keeping the user experience clean.

Browsers don’t visually indicate the ping attribute in any way to the user — a specification violation.

When the ping attribute is present, user agents should clearly indicate to the user that following the hyperlink will also cause secondary requests to be sent in the background, possibly including listing the actual target URLs.

HTML Standard (whatwg.org)

Not to mention a privacy concern, which is why browsers like Firefox refuse to enable this feature by default.

In Firefox Google sticks with the mousedown event approach:

There are many reasons not to disable JavaScript in 2023, but even if you do, Google will simply replace the href with a direct link to www.google.com/url.

HTML
<a href="/url?sa=t&source=web&rct=j&url=https://codingbeautydev.com/blog/vscode-tips-tricks..."> 10 essential VS Code tips and tricks for greater productivity </a>

So, there’s really no built-in way to avoid this mostly invisible tracking.

Even the analytics are highly beneficial for Google and site owners in improving result relevancy and site quality, as users we should be aware of the existence and implications of these tracking methods.

As technology becomes more integrated into our lives, we will increasingly have to choose between privacy and convenience and ask ourselves whether the trade-offs are worth it.

Nobody wants to use these Array methods😭

There’s so much more to arrays than map()filter()find(), and push() .

But most devs are completely clueless about this — several powerful methods they’re missing out on.

Check these out:

1. copyWithin()

Array copyWithin() copies a part of an array to another position in the same array and returns it without increasing its length.

JavaScript
const array = [1, 2, 3, 4, 5]; // copyWithin(target, start, end) // replace arr with start..end at target // a. target -> 3 (index) // b. start -> 1 (index) // c. end -> 3 (index) // start..end -> 2, 3 const result = array.copyWithin(3, 1, 3); console.log(result); // [1, 2, 3, 2, 3]

end parameter is optional:

JavaScript
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // "end" not specified so last array index used // target -> 0 (index) // start..end -> 6, 7, 8, 9, 10 const result = array.copyWithin(0, 5); // [6, 7, 8, 9, 10, 6, 7, 8, 9, 10] console.log(result);
JavaScript
const array = [1, 2, 3, 4, 5]; // Copy numbers 2, 3, 4, and 5 (cut off at index 4) const result = array.copyWithin(3, 1, 6); console.log(result); // [1, 2, 3, 2, 3]

2. at() and with()

at() came first and with() came a year after that in 2023.

They are the functional and immutable versions of single-element array modification and access.

JavaScript
const colors = ['pink', 'purple', 'red', 'yellow']; console.log(colors.at(1)); // purple console.log(colors.with(1, 'blue')); // ['pink', 'blue', 'red', 'yellow'] // Original not modified console.log(colors); // ['pink', 'purple', 'red', 'yellow']

The cool thing about these new methods is how they let you get and change element values with negative indexing.

3. Array reduceRight() method

Works like reduce() but the callback goes from right to left instead of left to right:

JavaScript
const letters = ['b', 'e', 'a', 'u', 't', 'y']; const word = letters.reduce((word, letter) => word + letter, ''); console.log(word); // beauty // Reducer iterations // 1. ('', 'y') => '' + 'y' = 'y' // 2. ('y', 't') => 'y' + 't' = 'yt'; // 3. ('yt', 'u') => 'ytu'; // ... // n. ('ytuae', 'b') => 'ytuaeb'; const wordReversed = letters.reduceRight((word, letter) => word + letter, ''); console.log(wordReversed); // ytuaeb

Here’s another great scenario for reduceRight():

JavaScript
const thresholds = [ { color: 'blue', threshold: 0.7 }, { color: 'orange', threshold: 0.5 }, { color: 'red', threshold: 0.2 }, ]; const value = 0.9; const threshold = thresholds.reduceRight((color, threshold) => threshold.threshold > value ? threshold.color : color ); console.log(threshold.color); // red

4. Array findLast() method

New in ES13: find array item starting from last element.

Great for cases where where searching from end position produces better performance than with find()

Example:

JavaScript
const memories = [ // 10 years of memories... { date: '2020-02-05', description: 'My first love' }, // ... { date: '2022-03-09', description: 'Our first baby' }, // ... { date: '2024-01-25', description: 'Our new house' }, ]; const currentYear = new Date().getFullYear(); const query = 'unique'; const milestonesThisYear = events.find( (event) => new Date(event.date).getFullYear() === currentYear && event.description.includes(query) );

This works but as our target object is closer to the tail of the array, findLast() should run faster:

JavaScript
const memories = [ // 10 years of memories... { date: '2020-02-05', description: 'My first love' }, // ... { date: '2022-03-09', description: 'Our first baby' }, // ... { date: '2024-01-25', description: 'Our new house' }, ]; const currentYear = new Date().getFullYear(); const query = 'unique'; const milestonesThisYear = events.findLast( (event) => new Date(event.date).getFullYear() === currentYear && event.description.includes(query) );

Another use case for findLast() is when we have to specifically search the array from the end to get the correct element.

For example, if we want to find the last even number in a list of numbers, find() would produce a totally wrong result:

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; // gives 14, instead of 10 const lastEven = nums.find((value) => value % 2 === 0); console.log(lastEven); // 14

But findLast() will start the search from the end and give us the correct item:

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; const lastEven = nums.findLast((num) => num % 2 === 0); console.log(lastEven); // 10

5. toSorted(), toReversed(), toSpliced()

ES2023 came fully packed with immutable versions of sort(), reverse(), and splice().

Okay maybe splice() isn’t used as much as the others, but they all mutate the array in place.

JavaScript
const original = [5, 1, 3, 4, 2]; const reversed = original.reverse(); console.log(reversed); // [2, 4, 3, 1, 5] (same array) console.log(original); // [2, 4, 3, 1, 5] (mutated) const sorted = original.sort(); console.log(sorted); // [1, 2, 3, 4, 5] (same array) console.log(original); // [1, 2, 3, 4, 5] (mutated) const deleted = original.splice(1, 2, 7, 10); console.log(deleted); // [2, 3] (deleted elements) console.log(original); // [1, 7, 10, 4, 5] (mutated)

Immutability gives us predictable and safer code; debugging is much easier as we’re certain variables never change their value.

Arguments are exactly the same, with splice() and toSpliced() having to differ in their return value.

JavaScript
const original = [5, 1, 3, 4, 2]; const reversed = original.toReversed(); console.log(reversed); // [2, 4, 3, 1, 5] (copy) console.log(original); // [5, 1, 3, 4, 2] (unchanged) const sorted = original.toSorted(); console.log(sorted); // [1, 2, 3, 4, 5] (copy) console.log(original); // [5, 1, 3, 4, 2] (unchanged) const spliced = original.toSpliced(1, 2, 7, 10); console.log(spliced); // [1, 7, 10, 4, 5] (copy) console.log(original); // [5, 1, 3, 4, 2] (unchanged)

6. Array lastIndexOf() method

The lastIndexOf() method returns the last index where a particular element can be found in an array.

JavaScript
const colors = ['a', 'e', 'a', 'f', 'a', 'b']; const index = colors.lastIndexOf('a'); console.log(index); // 4

We can pass a second argument to lastIndexOf() to specify an index in the array where it should stop searching for the string after that index:

JavaScript
const colors = ['a', 'e', 'a', 'f', 'a', 'b']; // Get last index of 'a' before index 3 const index1 = colors.lastIndexOf('a', 3); console.log(index1); // 2 const index2 = colors.lastIndexOf('a', 0); console.log(index2); // 0 const index3 = colors.lastIndexOf('f', 2); console.log(index3); // -1

7. Array flatMap() method

The flatMap() method transforms an array using a given callback function and then flattens the transformed result by one level:

JavaScript
const arr = [1, 2, 3, 4]; const withDoubles = arr.flatMap((num) => [num, num * 2]); console.log(withDoubles); // [1, 2, 2, 4, 3, 6, 4, 8]

Calling flatMap() on the array does the same thing as calling map() followed by a flat() of depth 1, but it`s a bit more efficient than calling these two methods separately.

JavaScript
const arr = [1, 2, 3, 4]; // flat() uses a depth of 1 by default const withDoubles = arr.map((num) => [num, num * 2]).flat(); console.log(withDoubles); // [1, 2, 2, 4, 3, 6, 4, 8]

Final thoughts

They are not that well-known (yet) but they have their unique uses and quite powerful.

10 powerful tools for faster web development

10 fantastic web dev tools to level up your productivity and achieve your coding goals faster than ever.

From colorful styling effects to faster typing, these tools will boost your workflow and make a lot of things easier.

1. React Glow

Create a playful glow that dances with the user’s mouse, adding a touch of magic to your interface:

Whenever I see effects like this on a page, it just gives me an aura of sophistication and quality.

2. Vite

Ditch Create React App and make development a breeze with Vite.

Say hello to lightning-fast hot module replacement and built-in TypeScript support.

And absolutely no need to worry about outdated dependencies or vulnerable packages – unlike with Create React App:

On the other hand, this is what I get for using dinosaur Create React App in one my Electron projects:

3. TODO Highlight for VS Code

With TODO highlight I can quickly add TODO comments anywhere in my codebase and keep track of all them effortlessly.

You quickly add a TODO with comment prefixed with TODO:

Then view in the Output window after running the TODO-Highlight: List highlighted annotations command:

I find it a great alternative to storing them in a to-do list app, especially when they’re something very low-level and contextual, for example: “TODO: Use 2 map()’s instead of reduce()”.

4. Rough Notation

Powerful JS library for creating and animating colorful annotations on a web page with a hand-drawn look and feel.

When I see this I see a human touch in the deliberate imperfections; it stands out.

So there’s underline, box, circle, highlight, strike-through… many many annotation styles to choose from with duration and color customization options.

5. JavaScript (ES6) Code Snippets for VS Cod

VS Code extension fully loaded with heaps of time-saving JavaScript code snippets for ES6.

Snippets like imp and imd:

A demo of how to use the JavaScript (ES6) Code Snippets extension.

6. background-removal-js

This free, browser-based JavaScript library lets you easily remove backgrounds from your images all while keeping your data private.

7. VanJS

This is a super small and simple library for building user interfaces.

It uses plain JavaScript and the built-in DOM functionality, just like React. But unlike React it doesn’t need any special syntax for defining UI elements.

JavaScript
// Reusable components can be just pure vanilla JavaScript functions. // Here we capitalize the first letter to follow React conventions. const Hello = () => div( p("👋Hello"), ul( li("🗺️World"), li(a({href: "https://wp.codingbeautydev.com/"}, "💻Coding Beauty")), ), ) van.add(document.body, Hello()) // Alternatively, you can write: // document.body.appendChild(Hello())

8. Mailo

This drag-and-drop builder makes crafting beautiful, responsive emails a breeze. No more coding headaches, just watch your emails light up inboxes across all devices.

9. Color Names

Dive into a treasure trove of over 30,000 color names, meticulously gathered from across the web and lovingly enriched by thousands of passionate users.

Find the perfect shade to ignite your creativity and bring your designs to life

10. Flowbite Icons

Unleash a treasure trove of 450+ stunning SVG icons, all open-source and ready to bring your web projects to life.

Whether you prefer bold solids or crisp outlines, these icons seamlessly integrate with Flowbite and Tailwind CSS, making customization a breeze.

Final thoughts

Use these awesome tools to level up your productivity and developer quality of life.

Interviewer: You are clueless about for loops😂

Interviewer: What does func() print in this code?

JavaScript
const arr = [1, 2, 3, 4]; for (let i = 0; i < arr.length; i++) { func(arr[i]); arr.splice(0, 1); } function func(item) { console.log(item); }

A huge grin appeared on Jimmy’s face.

After all the many rounds of interviews he’d finally gotten to this last question, and it was easier than he ever thought it would be.

Get this right and the job was assured.

“It prints 1, 2, 3, and 4”, he confidently proclaimed.

Unfortunately this turned out to be a huge mistake, and the interview was ended immediately.

“But how?! What else would it print? I got it correctly!”, He fumed at the interviewer.

“No, you didn’t. Your foundation of JavaScript is clearly not solid enough”.

“Okay okay wait, I get it now — splice() mutates the array on every iteration right?

I see it now! I forgot that element indexes change when you remove elements.

Let me work this out:”

Plain text
iter 1: [1, 2, 3, 4], index: 0, print: 1 iter 2: [2, 3, 4], index: 1, print: 3 iter 3: [3, 4], index: 2, print: undefined iter 4: [4], index: 3, print: undefined

“Oh so it really prints 1, 3, undefined, and undefined”. He looked up in desperate reassurance.

It was the interviewer’s turn to grin.

“Wrong again”.

Alright, enough of this nonsense.

“What the hell is wrong with you?”, He boomed as he shot up from his seat.

“I’ve worked my ass off for your stupid company and I haven’t even gotten the job!

And now you’re giving me this simple question for what? To insult my intelligence? Test my confidence? You think I don’t how splice() works? Or for loops, like what do you take me for?”

“You think I give a damn about your useless organization? You think I gave up my $200K job at Google for this crap…”, the ranting and raving went on and on…

The interviewer’s smile never wavered as the candidate continued his tirade, his words increasingly filled with frustration. He stood, clenching and unclenching his fists, the tension palpable.

“I don’t need this! I’ve mastered JavaScript, built complex applications, and solved problems you couldn’t even comprehend. And now this? A trick question? Who even writes code like that in production? This is absurd!”

The interviewer calmly leaned back in his chair and crossed his arms, still maintaining that infuriating, knowing smile.

“Are you finished?”, the interviewer asked after a long pause, the candidate breathing heavily.

Jimmy glared at him, his heart racing, his fists clenched, ready for another volley. But the interviewer’s calm demeanor somehow doused the fire in his chest.

“My friend, the issue is not with splice() or how the loop behaves.

The core of the problem lies in how you’re thinking about the array’s length in the for loop”

“What the hell are you talking about?” he sat back down.

“Let me show you”, he came closer to Jimmy’s laptop.

JavaScript
const arr = [1, 2, 3, 4]; for (let i = 0; i < arr.length; i++) { console.log('here'); arr.splice(0, 1); }

“Do you see how many times the loop ran?”

“Wait… wha… how are there only 2 iterations?”, Jimmy couldn’t believe his eyes.

“You see what I said about you lacking a fundamental understanding of JavaScript? You are clueless about for loops”

“Don’t you realize that the length property is recalculated every time the loop runs?”

Jimmy’s face flushed as the realization sank in.

“So I hope you can see what really happens in the code:”

JavaScript
const arr = [1, 2, 3, 4]; for (let i = 0; i < arr.length; i++) { func(arr[i]); arr.splice(0, 1); } function func(item) { console.log(item); }

It prints just 1 and 3:

“People like you are so used to looping to a constant number. You didn’t even consider the possibility that arr.length could change, like here again:”

JavaScript
let n = 10; for (let i = 0; i < n; i++) { console.log(n--); }

Jimmy nodded slowly, his anger replaced by quiet contemplation.

“I should have seen it”.

“We all have rough moments. Take this as a chance to reflect and improve.”

Jimmy shook his hand, humbled. This interview was still over so he rose up to leave.

“Alright, so um… do I still get the job?”

The interviewer burst into a hysterical feat of laughter.

“Don’t push it my man”.

The 5 most transformative JavaScript features from ES11

JavaScript has come a long way in the past 10 years with brand new feature upgrades in each one.

Let’s take a look at the 5 most significant features that arrived in ES11; and see the ones you missed.

1. Modularization on the fly: Dynamic imports

ES11 was the awesome year when import could now act as function, like require().

An async function.

Keeping imports at the top level was no longer a must; We could now easily resolve the module’s name at compile time.

Loading modules optionally and only when needed for high-flying performance…

JavaScript
async function asyncFunc() { if (condition) { const giganticModule = await import('./gigantic-module'); } }

Loading modules based on user or variable input…

JavaScript
import minimist from 'minimist'; const argv = minimist(process.argv.slice(2)); viewModule(argv.name); async function viewModule(name) { const module = await import(name); console.log(Object.keys(module)); }

It’s also great for using ES modules that no longer support require():

JavaScript
// ❌ require() of ES modules is not supported const chalk = require('chalk'); console.log(chalk.blue('Coding Beauty')); (async () => { // ✅ Runs successfully const chalk = (await import('chalk')).default; console.log(chalk.blue('Coding Beauty')); })();

2. Promise.allSettled()

But we already had Promise.all() to wait for all the Promises in a list to resolve?

JavaScript
const responses = await Promise.all([ fetch(endpoint1), fetch(endpoint2), ]);

So what was the point of this, right?

But no, allSettled() turns out to be quite different from all().

Promise.all(): If even a single Promise in the list fails, everything fails.

But you see the problem: We’d have no idea if one failed and which succeeded.

What if you want to retry the failed errors until they do succeed? You’re stuck.

Until you turn to Promise.allSettled():

❌ Before ES11:

JavaScript
// ❌ Promise.all() async function fetchData() { const apiUrl = 'api.tariibaba.com'; const endpoint1 = `${apiUrl}/route1`; const endpoint2 = `${apiUrl}/route2`; try { const responses = await Promise.all([ fetch(endpoint1), fetch(endpoint2), ]); } catch (err) { // ❌ Which failed & which succeeded? We have no idea console.log(`error: ${err}`); } // ... }

✅ After ES11:

Promise.allSettled(): Wait for every Promise to fail or resolve.

JavaScript
// ✅ Promise.allSettled() async function fetchData() { const apiUrl = 'api.tariibaba.com'; const endpoint1 = `${apiUrl}/route1`; const endpoint2 = `${apiUrl}/route2`; try { const promises = await Promise.allSettled([ fetch(endpoint1), fetch(endpoint2), ]); const succeeded = promises.filter( (promise) => promise.status === 'fulfilled' ); const failed = promises.filter( (promise) => promise.status === 'rejected' ); // ✅ now retry failed API requests until succeeded? } catch (err) { // We don't need this anymore! console.log(`error: ${err}`); } // ... }

3. Optional chaining

?. is all over the place now but it all started from ES11.

We’ve been checking vars for null since the dawn of time but it gets pretty cumbersome when we’re null-checking nested properties.

❌ Before ES11:

JavaScript
const a = { b: { c: { d: { site: 'codingbeautydev.com', name: null, }, }, }, }; // ❌ Must check every property for null if (a && a.b && a.b.c && a.b.c && a.b.c.d.site) { console.log(a.b.c.d.site); }

✅ After ES11:

With the optional chaining operator:

JavaScript
// `?.` auto-checks every property for null // if any prop in the chain is null, short-circuit // and return null if (a?.b?.c?.d?.site) { console.log(a.b.c.d.site); }

4. Nullish coalescing

?? was one of the most impactful JavaScript additions.

A powerful to set a variable to a default value and use it at the same time.

❌ Before ES11:

We repeat the variable unnecessarily:

JavaScript
console.log(str1 ? str1 : 'codingbeautydev.com'); // coding is cool console.log(str2 ? str2 : 'codingbeautydev.com'); // codingbeautydev.com

✅ After ES12:

?? keeps things clean and readable:

JavaScript
const str1 = 'coding is cool'; const str2 = null; console.log(str1 ?? 'codingbeautydev.com'); // coding is cool console.log(str2 ?? 'codingbeautydev.com'); // codingbeautydev.com

It coalesces.

Left and right combine to form a non-null whole:

5. Go big or go home: Big Ints

The name BigInt gives it away: loading up on humongous integer values:

JavaScript
const bigInt = 240389470239846028947208942742089724204872042n; const bigInt2 = BigInt( '34028974029641089471947861048917649816048962' ); console.log(typeof bigInt); console.log(bigInt); console.log(typeof bigInt2); console.log(bigInt2); console.log(bigInt * bigInt2);

Because normal integers can’t:

JavaScript
// ✖️ Stored as double const normalInt = 240389470239846028947208942742089724204872042; const normalInt2 = 34028974029641089471947861048917649816048962; console.log(typeof normalInt); console.log(normalInt); console.log(typeof normalInt2); console.log(normalInt2); // ✖️ Precision lost console.log(normalInt * normalInt2);

Final thoughts

These are the juicy new JavaScript features that arrived in the ES12.

Use them to boost your productivity as a developer and write cleaner code with greater conciseness, expressiveness and clarity.

This new JavaScript operator is an absolute game changer

With the new safe assignment ?= operator you’ll stop writing code like this:

JavaScript
// ❌ Before: // ❌ Deep nesting of try-catch for different errors async function fetchData() { try { const response = await fetch('https://api.codingbeautydev.com/docs'); try { const data = await response.json(); return data; } catch (parseError) { console.error(parseError); } } catch (networkError) { console.error(networkError); } }

And start writing code like this:

JavaScript
// ✅ After: async function fetchData() { const [networkError, response] ?= await fetch('https://codingbeautydev.com'); if (networkError) return console.error(networkError); const [parseError, data] ?= await response.json(); if (parseError) return console.error(parseError); return data; }

We’ve completely eradicated the deep nesting. The code is far more readable and cleaner.

Instead of getting the error in the clunky catch block:

JavaScript
async function doStuff() { try { const data = await func('codingbeautydev.com'); } catch (error) { console.error(error); } }

Now we do everything in just one line.

Instead of failing loudly and proudly, ?= tells the error to shut up and let us decide what do with it.

JavaScript
// ✅ if there's error: `err` has value, `data` is null // ✅ no error: `err` is null, `data has value async function doStuff() { const [err, data] ?= await func('codingbeautydev.com'); }

We can tell it to get lost:

JavaScript
async function doStuff() { // 👇 it's as good as gone here const [, data] ?= await func('codingbeautydev.com'); // ... }

We can announce it to the world and keep things moving:

JavaScript
async function doStuff() { const [err, data] ?= await func('codingbeautydev.com'); if (err) { console.error(err); } // ... }

Or we can stop immediately:

JavaScript
// `err` is null if there's no error async function doStuff() { const [err, data] ?= await func('codingbeautydev.com'); if (err) return; }

Which makes it such a powerful tool for creating guard clauses:

JavaScript
// ✅ avoid nested try-catch // ✅ avoid nested ifs function processFile() { const filename = 'codingbeautydev.com.txt'; const [err, jsonStr] ?= fs.readFileSync(filename, 'utf-8'); if (readErr) { return; } const [jsonErr, json] ?= JSON.parse(jsonStr); if (jsonErr) { return; } const awards = json.awards.length; console.log(`🏅Total awards: ${awards}`); }

And here’s one of the very best things about this new operator.

There are several instances where we want a value that depends on whether or not there’s an exception.

Normally you’ll use a mutable var outside the scope for error-free access:

JavaScript
function writeTransactionsToFile(transactions) { // ❌ mutable var let writeStatus; try { fs.writeFileSync( 'codingbeautydev.com.txt', transactions ); writeStatus = 'success'; } catch (error) { writeStatus = 'error'; } // do something with writeStatus... }

But this can be frustrating, especially when you’re trying to have immutable code and the var was already const before the time came to add try-catch.

You’d have to wrap it try, then remove the const, then make a let declaration outside the try, then re-assign again in the catch

But now with ?=:

JavaScript
function writeTransactionsToFile(transactions) { const [err, data] ?= fs.writeFileSync( 'codingbeautydev.com.txt', transactions ) const writeStatus = err ? 'error' : 'success' // // do something with writeStatus... }

We maintain our immutability and the code is now much more intuitive. Once again we’ve eradicated all nesting.

How does it work?

The new ?= operator calls the Symbol.result method internally.

So when we do this:

JavaScript
const [err, result] ?= func('codingbeautydev.com');

This is what’s actually happening:

JavaScript
// it's an assignment now const [err, result] = func[Symbol.result]( 'codingbeautydev.com' );

So you know what this means right?

It means we can make this work with ANY object that implements Symbol.result:

JavaScript
function doStuff() { return { [Symbol.result]() { return [new Error("Nope"), null]; }, }; } const [error, result] ?= doStuff();

But of course you can throw as always:

JavaScript
function doStuff() { throw new Error('Nope'); } const [error, result] ?= doStuff();

And one cool thing it does: if result has its own Symbol.result method, then ?= drills down recursively:

JavaScript
function doStuff() { return { [Symbol.result](str) { console.log(str); return [ null, { [Symbol.result]() { return [new Error('WTH happened?'), null]; } } ]; } } } const [error, result] ?= doStuff('codingbeautydev.com');

You can also use the object directly instead of returning from a function:

JavaScript
const obj = { [Symbol.result]() { return [new Error('Nope'), null]; }, }; const [error, result] ?= obj;

Although, where would this make any sense in practice?

As we saw earlier, ?= is versatile enough to fit in seamlessly with both normal and awaited functions.

JavaScript
const [error, data] ?= fs.readFileSync('file.txt', 'utf8'); const [error, data] ?= await fs.readFile('file.txt', 'utf8');

using

?= also works with the using keyboard to automatically clean up resources after use.

❌ Before:

JavaScript
try { using resource = getResource(); } catch (err) { // ... }

✅ After:

JavaScript
using [err, resource] ?= getResource();

How to use it now

While we wait for ?= to become natively integrated into JavaScript, we can start it now with this polyfill:

But you can’t use it directly — you’ll need Symbol.result:

JavaScript
import 'polyfill.js'; const [error, data] = fs .readFileSync('codingbeautydev.com.txt', 'utf-8') [Symbol.result]();

Final thoughts

JavaScript error handling just got much more readable and intuitive with the new safe assignment operator (?=).

Use it to write cleaner and more predictable code.

Why does [] == ![] return TRUE in JavaScript?

It’s hard to believe.

How can an Array not be an Array?

It makes no sense — [] is truthy and ![] should be false.

So how can [] equal false?

And this somehow doesn’t happen for other types like strings and numbers:

Are JavaScript arrays broken?

What happened here

Dump all the blame on the dangerous == operator.

This is just another instance of why we always tell new JavaScript devs to NEVER use it (ever).

Especially if they’ve already been coding with stubbornly strict languages like C#.

At first glance, it doesn’t seem like there’s anything wrong with ==:

Plain text
// C# int num = 2; Console.WriteLine(num == 10); // false Console.WriteLine(num == 2); // true
JavaScript
// JS let num = 2; console.log(num == 10); // false console.log(num == 2); // true

But now look what happens here:

Plain text
// C# int num = 2; // ❌ Error: can't compare int and string Console.WriteLine(num == "2"); // false

But look what happens in JavaScript:

JavaScript
// JS let num = 2; console.log(num == "2"); // true?

JavaScript auto-casts the string into a number!

This is one of the many frustrations people have with JavaScript that made TypeScript come along.

Instead of just failing and stopping us from doing dumb stuff, it just goes “Mhmm okay, if you say so…”

JavaScript
// Huh? console.log(null == undefined); // true // Converts array to string console.log([1,2,3] == "1,2,3"); // true (seriously?)

So what do you think REALLY happens in [] == ![], behind the scenes?

First of all, empty arrays are truthy in JavaScript, so ! acts on it to make it false

JavaScript
[] == false

So now surprise surprise, we suddenly find ourselves comparing an Array to Boolean. Obviously not gonna end well.

As we now know, JS doesn’t care so it just goes ahead — this time casting the Boolean to the equivalent number

JavaScript
[] == Number(false) [] == 0

Next, thanks to some garbage rules that you don’t need to ever know, [] turns into… an empty string?

JavaScript
// don't ask "" == 0

And now finally it converts "" into… a Number:

JavaScript
0 == 0 // true

So what’s the solution to avoid this nonsense?

Always use the strict equality operator === (and I mean always).

JavaScript
// font ligatures make triple equal look nicer console.log(2 == '2'); // ❌ true console.log(2 === '2'); // ✅ false console.log([] == ![]); // ❌ true console.log([] === ![]); // ✅ false

There is no scenario imaginable in the universe where == can be used that === can’t be

And now with ===, your lovely VS Code editor suddenly comes alive to stop us from doing something like this:

It still compiles though — unless you use TypeScript

But before it was asleep:

But what about [] == []?

Okay this makes sense, but what then could possibly explain this:

Surely the == can’t be blamed here. They have the same type, don’t they?

Yes they do.

The problem is that JavaScript compares arrays by reference. Not by value.

They may have exactly the same value, but as long they don’t refer the same object in memory, they will never be equal in the eyes of == and ===

JavaScript
// these are creating new array objects // on the fly, stored at different locations console.log([] == []); // false console.log([] === []); // false // store new object this time const arr = []; // reuse it const tari = arr; // both vars refer to the same object in memory console.log(arr == tari); // true console.log(arr === tari); // true

And this is how it is for objects in general too:

JavaScript
console.log({} === {}); // false const person1 = { name: 'tari ibaba' }; const person2 = { name: 'tari ibaba' }; const person3 = person1; // Only person1 and person3 point to the same object console.log(person1 === person2); // false console.log(person1 === person3); // true console.log(person2 === person3); // false

But of course, this isn’t the case for our core primitive values — strings, numbers, and booleans:

JavaScript
console.log('tari ibaba' === 'tari ibaba'); // As we saw previously console.log(2 === 2);

So what do you do when you want to compare arrays by their element values?

If it’s sorted you can use JSON.stringify():

JavaScript
function arraysEqual(array1, array2) { return JSON.stringify(array1) === JSON.stringify(array2); }

Otherwise, you go for the more generic length and every() combo:

JavaScript
function arraysEqual(array1, array2) { return ( array1.length === array2.length && array1.every((value, index) => value === array2[index]) ); }

Final thoughts

== is just one example of JavaScript’s looseness making it do things that make no sense in the real world.

Moral lesson: Always use strict equality, use TypeScript, and prioritize modern features.

The 5 most transformative JavaScript features from ES12

ES12 was truly an amazing upgrade.

Packed with valuable features that completely transformed the way we write JavaScript.

Code became cleaner, shorter, and easier to write.

Let’s check them out and see the ones you missed.

1. Promise.any()

Before ES12, we already had Promise.all() and Promise.allSettled() to wait for an entire group of Promises.

There were several times when we’d have several Promises but only be interested in whichever one resolved first.

So Promise.any() had to come into the picture:

JavaScript
async function getHelpQuickly() { const response = await Promise.any([ cautiousHelper(), kindHelper(), wickedHelper(), ]); console.log(response); // Of course! } async function cautiousHelper() { await new Promise((resolve) => { setTimeout(() => { resolve('Uum, oohkaay?'); }, 2000); }); } async function kindHelper() { return 'Of course!'; } function wickedHelper() { return Promise.reject('Never, ha ha ha!!!'); } // codingbeautydev.com

One interesting thing to note: even though any() resolves immediately, the app doesn’t end until all the Promises have resolved.

2. replaceAll()

Yes we already had replace() for quickly replace a substring within a string.

JavaScript
const str = 'JavaScript is so terrible, it is unbelievably terrible!!'; const result = str.replace('terrible', 'wonderful'); console.log(result); // JavaScript is so wonderful, it is unbelievably terrible!! // Huh? // codingbeautydev.com

But it only did so for the first occurrence of the substring unless you use a regex.

So ES12 gave us now we have replaceAll() to replace every single instance of that substring.

JavaScript
const str = 'JavaScript is so terrible, it is unbelievably terrible.'; const result = str.replaceAll('terrible', 'wonderful'); console.log(result); // JavaScript is wonderful, it is unbelievably wonderful. // Now you're making sense! // codingbeautydev.com

3. WeakRefs

As from ES12, a JavaScript variable can either be a strong reference or a weak reference.

What are these?

The Strong Refs are our normal everyday variables. But the Weak Refs need to be explicitly created with WeakRef():

JavaScript
const strongRef = { name: 'Tari Ibaba' } const weakRef = new WeakRef(strongRef); // codingbeautydev.com

JS variables are just references to the actual object in memory.

That’s why we can have multiple references to the same object.

JavaScript
const person = { name: 'Tari Ibaba' }; const me = person; // modifies the actual object that `me` points to person.site = 'codingbeautydev.com'; console.log(me.site); // codingbeautydev.com

But what’s the difference between a strong ref and a weak ref?

Well in programming we have something garbage collection, which is when unneeded objects are removed from memory to save resources.

In JavaScript, objects are automatically marked for garbage collected when all the strong ref variables pointing to it have become unreachable — out of scope:

The object person and me both point to is put on the destruction queue once func() runs.

JavaScript
func(); // 💡`person` and `me` are unreachable here function func() { // 💡this object will be marked for garbage collection // after this function runs const person = { name: 'Tari Ibaba' }; const me = person; person.site = 'codingbeautydev.com'; console.log(me.site); // codingbeautydev.com }

But look what happens here:

Even person went out of scope after func() finished, we still had me, a global strong reference.

JavaScript
let me; func(); // 💡one strong reference (`me`) is still reachable // ✅ Can always access object console.log(me.site); function func() { // 💡this object will NOT be garbage collected // after this function runs const person = { name: 'Tari Ibaba' }; me = person; person.site = 'codingbeautydev.com'; console.log(me.site); // codingbeautydev.com }

But what if me was a weak reference?

Now after func() executes, person would be the only strong reference to the object.

So the object will be marked for garbage collection:

JavaScript
let me; func(); // No strong references reachable // ❌ Bad idea: object may not exist console.log(me.deref().site); function func() { // 💡this object will be marked for garbage collection const person = { name: 'Tari Ibaba' }; me = new WeakRef(person); person.site = 'codingbeautydev.com'; console.log(me.deref().site); // codingbeautydev.com }

So why do we need weak references?

The biggest use case for them is caching.

Look what happens here: processData() runs and we have a new object stored in our cache.

Even though data becomes unreachable, the object will never be garbage collected because it has a strong reference in the cache.

JavaScript
let cache = new Map(); processData(); function processData() { const url = 'api.tariibaba.com'; const data = fetchData(url); // process data for app... } async function fetchData(url) { // check cache const saved = cache.get(url); if (!saved) { const data = await (await fetch(url)).json(); cache.set(url, data); } return saved; }

But what if I want the object to be freed up after processData() exits?

I would use a WeakRef as the Map values instead:

JavaScript
let cache = new Map(); processData(); function processData() { const url = 'api.tariibaba.com'; const data = fetchData(url); // process data for app... // 💡the object will only exist in cache // in this function } async function fetchData(url) { // deref weak ref const saved = cache.get(url).deref(); if (!saved) { const data = await (await fetch(url)).json(); // ✅ Use a WeakRef instead cache.set(url, new WeakRef(data)); } return saved; }

4. Logical assignment operators

Lovely syntactic sugar from ES12.

We use them like this:

JavaScript
left ??= right; left ||= right; left &&= right; // codingbeautydev.com

Exactly the same as:

JavaScript
left = (left ?? right); left = (left || right); left = (left && right); // codingbeautydev.com

??=. Quickly assign a value to a variable *if* it is null or undefined (“nullish”).

JavaScript
user.preferredName ??= generateDumbUserName();

||=. Like ??=, but assigns the value for any falsy value (0undefinednull''NaN, or false).

JavaScript
user.profilePicture ||= "/angry-stranger.png";

And then &&=. Something like the reverse; only assigns when the value is truthy (not falsy).

JavaScript
user.favoriteLanguage = await setFavoriteLanguage(input.value); user.favoriteLanguage &&= 'Assembly'; // You're lying! It's Assembly!

5. Numeric separators

Tiny but impactful new addition that made big number literals more readable and human-friendly:

JavaScript
const isItPi = 3.1_415_926_535; const isItAvagadro = 602_214_076_000_000_000_000_000; const isItPlanck = 6.626_070_15e-34; const isItG = 6.674_30e-11; // Works for other number bases too... // codingbeautydev.com

The compiler completely ignores those pesky underscores — they’re all for you, the human!

Final thoughts

These are the juicy new JavaScript features that arrived in the ES12.

Use them to boost your productivity as a developer and write cleaner code with greater conciseness, expressiveness and clarity.

Promise.all() vs Promise.allSettled() in JS: The little-known difference

I can’t believe some developers think they are interchangeable.

They are not.

all() and allSettled() are much more different than you think.

So let’s say I decided to singlehandedly create my own ambitious new music app to finally take down Spotify.

I’ve worked tirelessly to make several music deals and have all the backend data ready.

Now I’m creating the app’s homepage: a shameless carbon copy of Spotify showing off daily mixes, recently played, now playing, and much more.

All this data will come from different API URLs.

Of course I wouldn’t be naive enough to fetch from all the URLs serially and create a terrible user experience (or would I?)

JavaScript
async function loadHomePage() { const apiUrl = 'music.tariibaba.com/api'; const dailyMixesUrl = `${apiUrl}/daily`; const nowPlayingUrl = `${apiUrl}/nowPlaying`; // ❌ Serial loading const dailyMixes = await ( await fetch(dailyMixesUrl) ).json(); const nowPlaying = await ( await fetch(nowPlayingUrl) ).json(); return { dailyMixes, nowPlaying }; }

No I wouldn’t.

So instead I use use Promise.all() to fetch from all these URLs at once:

Much better, right?

JavaScript
async function loadHomePage() { const apiUrl = 'music.tariibaba.com/api'; const dailyMixesUrl = `${apiUrl}/daily`; const nowPlayingUrl = `${apiUrl}/nowPlaying`; // Promise.all() ?? await Promise.all([ fetch(dailyMixesUrl), fetch(nowPlayingUrl), ]); return { dailyMixes, nowPlaying }; }

Wrong.

I just made the classic developer error of forgetting to handle the error state.

Let me tell you something: When Promise.all() fails, it fails:

JavaScript
async function loadHomePage() { const apiUrl = 'music.tariibaba.com/api'; const dailyMixesUrl = `${apiUrl}/daily`; const nowPlayingUrl = `${apiUrl}/nowPlaying`; // Promise.all() ?? try { await Promise.all([ fetch(dailyMixesUrl), fetch(nowPlayingUrl), ]); } catch (err) { // ❌ Which failed & which succeeded? We have no idea console.log(`error: ${err}`); } return { dailyMixes, nowPlaying }; }

What do you when one of those requests fail? Am I just going to leave that UI section blank and not try again?

But look what Promise.allSettled() would have done for us:

JavaScript
async function loadHomePage() { const apiUrl = 'music.tariibaba.com/api'; const dailyMixesUrl = `${apiUrl}/daily`; const nowPlayingUrl = `${apiUrl}/nowPlaying`; // ... // Promise.all() ?? try { const promises = await Promise.allSettled([ fetch(dailyMixesUrl), fetch(nowPlayingUrl), // ... ]); const succeeded = promises.filter( (promise) => promise.status === 'fulfilled' ); const failed = promises.filter( (promise) => promise.status === 'rejected' ); // ✅ now retry failed API requests until succeeded } catch (err) { // We don't need this anymore! console.log(`error: ${err}`); } return { dailyMixes, nowPlaying }; }

Now I could easily set up a clever system to tirelessly retry all the failed requests until they all work.

JavaScript
async function loadHomePage() { const apiUrl = 'music.tariibaba.com/api'; const dailyMixesUrl = `${apiUrl}/daily`; const nowPlayingUrl = `${apiUrl}/nowPlaying`; // Promise.all() ?? const pending = [ () => fetch(dailyMixesUrl), () => fetch(nowPlayingUrl), ]; while (pending.length > 0) { const promises = await Promise.allSettled( pending.map((pending) => pending()) ); const newPending = []; promises.forEach((promise, index) => { if (promise.status === 'rejected') { newPending.push(pending[index]); } }); pending = newPending; } return { dailyMixes, nowPlaying }; }

When all() is better

This is great, but I need to update a lot of data when a user plays a song.

The song’s stream count, the user’s history, recently played…

But the database I’m using is horrible and doesn’t have support for transactions / batched writes.

I need to find a way to make sure I can update all the data in their separate locations at the exact same time.

Luckily, Promise.all() is useful this time.

It’s all or nothing.

JavaScript
async function runTransaction(updates) { // updates are a list of DB actions try { // store db state, somehow... await Promise.all(updates); } catch (err) { // intelligently revert to previous state, somehow... } }

With all() I’m confident that if a single updates fails, it’s over.

And now I can even bring my smart auto-retry system here, but this time everything is getting retried, after this reversal.

Final thoughts

Promise.all() — If one of us fails, we all fail.

Promise.allSettled() — You are all on your own.