Tari Ibaba

Tari Ibaba is a software developer with years of experience building websites and apps. He has written extensively on a wide range of programming topics and has created dozens of apps and open-source libraries.

7 amazing new JavaScript features in ES14 (ES2023)

The world’s most popular programming language just got a new update.

Ever since 2015, a new JavaScript version has come out every year with tons of powerful features to make life much easier, and 2023 has been no different. Let’s look at what ES14 has to offer.

1. Array toSorted() method

Sweet syntactic sugar.

ES14 comes with a new toSorted() method that makes it easier to sort an array and return a copy without mutation.

So instead of doing this:

JavaScript
const nums = [5, 2, 6, 3, 1, 7, 4]; const clone = [...nums]; const sorted = clone.sort(); console.log(sorted); // [1, 2, 3, 4, 5, 6, 7]

We now get to do this:

JavaScript
const nums = [5, 2, 6, 3, 1, 7, 4]; const sorted = nums.toSorted(); console.log(sorted); // [1, 2, 3, 4, 5, 6, 7] console.log(nums); // [5, 2, 6, 3, 1, 7, 4]

And just like sort(), toSorted() takes a callback function that lets you decide how the sort should happen – ascending or descending, alphabetical or numeric.

JavaScript
const nums = [5, 2, 6, 3, 1, 7, 4]; const sorted = nums.toSorted((a, b) => b - a); console.log(sorted); // [7, 6, 5, 4, 3, 2, 1]

2. Hashbang grammar

What’s a hashbang?

Basically, it’s a sequence of characters in a file that tells the Linux Shell what interpreter or engine it should use to run the file. For example:

codingbeauty.sh
#!/bin/bash echo "How are you all doing today?"

With this hashbang, we can now easily run codingbeauty.sh directly in the shell, after using the chmod command to make the script executable.

Hashbangs also let us hide all the juicy implementation details in our scripts and indicate specific interpreter versions to execute the script files.

So with ES14, we can now do this effortlessly in JavaScript files, like this:

codingbeauty.js
#!/usr/bin/env node console.log("I'm doing great, how are you?");

3. Array toSpliced() method

Those immutability purists out there will no doubt be pleased with all these new Array methods.

toSorted() is to sort() as toSpliced() is to splice():

JavaScript
const colors = ['red', 'green', 'blue', 'yellow', 'pink']; const spliced = colors.toSpliced(1, 2, 'gray', 'white'); console.log(spliced); // [ 'red', 'gray', 'white', 'yellow', 'pink' ] // Original not modified console.log(colors); // ['red', 'green', 'blue', 'yellow', 'pink'];

Here toSpliced() removes 2 of the array elements, starting from index 1, and inserts 'gray' and 'white' in their place.

4. Symbols as WeakMap keys

WeakMaps; not very popular, are they?

Well, they’re a lot like Maps, except their keys can only contain non-primitive objects – no strings or numbers allowed here. These keys are stored as weak references, meaning the JavaScript engine can carry out garbage collection on the objects when it needs to if there is no other reference to the objects in memory apart from the keys.

One powerful use of WeakMaps is custom caching: by using objects as keys, you can associate cached values with specific objects. When the objects are garbage collected, the corresponding WeakMap entries are automatically removed, clearing the cache immediately.

JavaScript
const map = new Map(); const weakMap = new WeakMap(); const obj1 = { name: 'React' }; const obj2 = { name: 'Angular' }; map.set(obj1, 'Value for obj1 at Coding Beauty'); weakMap.set(obj2, 'Value for obj2 at Coding Beauty'); console.log(map.get(obj1)); // Output: Value for obj1 console.log(weakMap.get(obj2)); // Output: Value for obj2 obj1 = null; obj2 = null; console.log(map.get(obj1)); // Output: Value for obj1 console.log(weakMap.get(obj2)); // Output: undefined (obj2 has been garbage collected)

So ES14 makes it possible to define JavaScript Symbols as keys. This can make the role a key-value pair plays in a WeakMap clearer.

JavaScript
let mySymbol = Symbol('mySymbol'); let myWeakMap = new WeakMap(); let obj = { name: 'Coding Beauty' }; myWeakMap.set(mySymbol, obj); console.log(myWeakMap.get(mySymbol)); // Output: object

And what are they meant for?

5. Array toReversed() method

Another new Array method to promote immutability and functional programming.

The name’s self-explanatory: give me the reversed version of my array.

Before – with reverse().

JavaScript
const arr = [5, 4, 3, 2, 1] const reversed = arr.reverse(); console.log(reversed); // [1, 2, 3, 4, 5] // Original modified console.log(arr); // [1, 2, 3, 4, 5]

Now:

JavaScript
const arr = [5, 4, 3, 2, 1] const reversed = arr.toReversed(); console.log(reversed); // [5, 4, 3, 2, 1] // Original NOT modified console.log(arr); // [1, 2, 3, 4, 5]

6. Array find from last

Sure we can already use the Array find() method to find an element in an array that passes a specified test condition. And findIndex() will give us the index of such an element.

But find() and findIndex() both start searching from the first element of the array. What if it’ll be better for us to search from the last element instead?

Here we’re trying to get the item in the array with the value prop equal to y. With find() and findIndex():

JavaScript
const letters = [ { value: 'v' }, { value: 'w' }, { value: 'x' }, { value: 'y' }, { value: 'z' }, ]; const found = letters.find((item) => item.value === 'y'); const foundIndex = letters.findIndex((item) => item.value === 'y'); console.log(found); // { value: 'y' } console.log(foundIndex); // 3

This works, but as the target object is closer to the tail of the array, we could make this program run faster if we use the new ES2022 findLast() and findLastIndex() methods to search the array from the end.

JavaScript
const letters = [ { value: 'v' }, { value: 'w' }, { value: 'x' }, { value: 'y' }, { value: 'z' }, ]; const found = letters.findLast((item) => item.value === 'y'); const foundIndex = letters.findLastIndex((item) => item.value === 'y'); console.log(found); // { value: 'y' } console.log(foundIndex); // 3

Another use case might require that we specifically search the array from the end to get the correct item. For example, if we want to find the last even number in a list of numbers, find() and findIndex() 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); // gives 1, instead of 4 const lastEvenIndex = nums.findIndex((value) => value % 2 === 0); console.log(lastEven); // 14 console.log(lastEvenIndex); // 1

Yes, we could call the reverse() method on the array to reverse the order of the elements before calling find() and findIndex().

But this approach would cause unnecessary mutation of the array, as reverse() reverses the elements of an array in place. The only way to avoid this mutation would be to make a new copy of the entire array, which could cause performance problems for large arrays.

And this beside the fact that findIndex() would still not work on the reversed array, as reversing the elements would also mean changing the indexes they had in the original array. To get the original index, we would need to perform an additional calculation, which means writing more code.

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; // Copying the entire array with the spread syntax before // calling reverse() const reversed = [...nums].reverse(); // correctly gives 10 const lastEven = reversed.find((value) => value % 2 === 0); // gives 1, instead of 4 const reversedIndex = reversed.findIndex((value) => value % 2 === 0); // Need to re-calculate to get original index const lastEvenIndex = reversed.length - 1 - reversedIndex; console.log(lastEven); // 10 console.log(reversedIndex); // 1 console.log(lastEvenIndex); // 4

It’s in cases like where the findLast() and findLastIndex() methods come in handy.

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

This code is shorter and more readable. Most importantly, it produces the correct result.

7. Array with() method

Unlike the others, with() has no complementary mutating method. But once you see it in action, you’ll know that it’s the immutable approach to changing a single element.

In so many languages, you typically modify a single array element like this:

JavaScript
const arr = [5, 4, 7, 2, 1] // Mutates array to change element arr[2] = 3; console.log(arr); // [5, 4, 3, 2, 1]

But see what we can do now, in ES2023 JavaScript:

JavaScript
const arr = [5, 4, 7, 2, 1]; const replaced = arr.with(2, 3); console.log(replaced); // [5, 4, 3, 2, 1] // Original not modified console.log(arr); // [5, 4, 7, 2, 1]

Final thoughts

This year was all about easier functional programming and immutability.

Indeed, the reliability and consistency that immutability brings cannot be overstated. With the rise of declarative frameworks like React and Vue as well as Redux and other libraries, we’ve seen immutable JavaScript array methods explode in popularity; it’s only natural that we see more and more of them come baked into the language as it matures.

They stole the show in 2023, just like JavaScript took over a huge chunk of the language ecosystem a while back, with many millions of developers keeping that fire burning today. Let’s see what the future holds.

How to easily get the intersection of two sets in JavaScript

To get the intersection of two Sets in JavaScript, combine the filter() and has() array methods:

JavaScript
const set1 = new Set([1, 2, 3, 4, 5]); const set2 = new Set([4, 5, 6, 7, 8]); const intersection = new Set([...set1].filter(x => set2.has(x))); console.log(intersection); // Set {4, 5}

The Array filter() method tests each element in an array against a condition specified by a callback function and creates a new array filled with elements that pass the test. It doesn’t modify the original array.

JavaScript
const arr = [1, 2, 3, 4]; const filtered = arr.filter((num) => num > 2); console.log(filtered); // [ 3, 4 ]

The spread syntax (...) converts the Set to an array:

JavaScript
const set1 = new Set([1, 2, 3, 4, 5]); const arrayFromSet = [...set1]; console.log(arrayFromSet); // [1, 2, 3, 4, 5]

We can also do this with the Array from() method, which converts Sets and other iterables into arrays:

JavaScript
const set1 = new Set([1, 2, 3, 4, 5]); const arrayFromSet = Array.from(set1); console.log(arrayFromSet); // [1, 2, 3, 4, 5]

And the Set has() method tells us whether or not the Set contains a particular element.

JavaScript
const set = new Set(['coding', 'beauty', 'dev']); const hasElegance = set.has('elegance'); const hasBeauty = set.has('beauty'); console.log(hasElegance); // false console.log(hasBeauty); // true

Get intersection of two sets with Array forEach() method

Alternatively, we can use the Array forEach() method to get the intersection of two Sets in JavaScript.

JavaScript
const set1 = new Set([1, 2, 3, 4, 5]); const set2 = new Set([4, 5, 6, 7, 8]); const intersection = new Set(); set1.forEach(value => { if (set2.has(value)) { intersection.add(value); } }); console.log(intersection); // Set {4, 5}

The Set forEach loops through an array and calls a specified function on each element.

JavaScript
const fruits = new Set(['apple', 'orange', 'banana']); fruits.forEach((fruit) => { console.log(fruit.toUpperCase()); }); // Output 👇 // APPLE // ORANGE // BANANA

The Set add() method adds a new element to a Set:

JavaScript
const set = new Set([1, 2, 3]); console.log(set); // Set(3) { 1, 2, 3 } set.add(10); console.log(set); // Set(4) { 1, 2, 3, 10 }

Get intersection of two Sets with for..of loop

Anywhere you see the forEach(), you can use the for..of loop in its place:

JavaScript
const set1 = new Set([1, 2, 3, 4, 5]); const set2 = new Set([4, 5, 6, 7, 8]); const intersection = new Set(); for (const value of set1) { if (set2.has(value)) { intersection.add(value); } } console.log(intersection); // Set {4, 5}

Get intersection of two Sets with reduce() method

You can also use the reduce() method to get the intersection of two Set objects in JavaScript:

JavaScript
const set1 = new Set([1, 2, 3, 4, 5]); const set2 = new Set([4, 5, 6, 7, 8]); const intersection = [...set1].reduce((acc, value) => { if (set2.has(value)) { acc.add(value); } return acc; }, new Set()); console.log(intersection); // Set {4, 5}

The reduce() method calls a function on each element of an array to accumulate a resulting value:

JavaScript
const nums = [1, 2, 3, 4, 5]; const sum = nums.reduce((acc, num) => acc + num, 0); const product = nums.reduce((acc, num) => acc * num, 1); console.log(sum); // 15 console.log(product); // 120

How to quickly toggle (enable/disable) autosave in VS Code

To enable or disable autosave in VS Code, use the File > Auto Save option from the menu bar.

The `File > Auto Save` option in Visual Studio Code enables/disables autosave.

After this, the “dirty” indicator will no longer show up when you modify a saved file.

Before:

Visual Studio Code without autosave.
No autosave.

After:

Visual Studio Code with autosave.
Autosave enabled – the unsaved indicator no longer shows.

Why do we need autosave?

So we can stop mindlessly pressing Ctrl + S.

So we can save time, increase productivity, and be much more certain that we’re working with the latest changes.

Autosave is not perfect though; it’s up to you to weigh the pros and cons – which we comprehensively cover here.

What’s not to like about autosave?

Some of the cons we talk about in that autosave article:

  1. It wastes resources because it saves even when you’re not ready to view the results of your changes.
  2. It makes it harder to recover from unexpected errors, for example, making a buggy file change and accidentally closing the file.
  3. There’s no auto-formatting with auto-save.

Enable/disable autosave in VS Code Settings

Alternatively, we can disable autosave in Visual Studio Code using the Files: Auto Save setting in the Settings page.

You can easily navigate to this page with the gear icon at the top-left of the code editor:

You can open Settings with the bottom-left gear icon.

Once you get there, you can use the search bar to find the setting.

Using the `Files: Auto Save` option in VS Code Settings.

As you can see, Files: Auto Save can be one of four possible values, namely:

  1. off – self-explanatory: disable autosave.
  2. afterDelay: – the new value enables autosave with the File > Auto Save setting. autosaves the file sometime after your changes.
  3. onFocusDelay – autosaves the dirty file when you switch windows or tabs.
  4. onWindowChange – as the name implies, autosaves the unsaved file when you switch windows in the operating system.

So there are more customization options in the Settings page than in the menu bar.

Change autosave delay in VS Code

When Files: Auto Save is set to afterDelay, you can modify the autosave delay in Visual Studio Code with the Files: Auto Save Delay setting.

Modifying the autosave delay in VS Code.

You may be better off increasing the autosave delay instead of disabling autosave entirely, so VS Code still saves your work automatically, while minimizing the impact on system resources.

Enable/disable autosave in VS Code with Command Palette

To turn autosave on or off in Visual Studio Code, you can also use the File: Toggle Auto Save command, accessible from the Command Palette:

Toggling auto save in VS Code with the Command Palette.

How to quickly listen for a route/page change in Next.js

To detect a route change in Next.js:

  • app directory: use useEffect and usePathname.
  • pages directory: use useEffect and useRouter.

Listen for route/page change in Next.js app directory

To listen for a route change in Next.js 13 app directory, use the useEffect and the usePathname hooks. The action in useEffect will run anytime the pathname from usePathname changes.

src/app/route-change-listener.tsx
'use client'; import { usePathname } from 'next/navigation'; import { useEffect, useState } from 'react'; export function RouteChangeListener() { const pathname = usePathname(); const [changes, setChanges] = useState(0); useEffect(() => { console.log(`Route changed to: ${pathname}`); setChanges((prev) => prev + 1); }, [pathname]); return <></>; }

Here we log to the console and change the state of the component when the route changes.

The URL change listener logs to the console when the route changes.
The URL change listener logs to the console when the route changes.

Page change listener must persist with client-side routing

For this detection to work, the component containing this useEffect needs to be somewhere in the DOM where it will not get unmounted with client-side navigation.

In the Next.js 13 app directory, this could be the layout.tsx file:

src/app/layout.tsx
import { Metadata } from 'next'; import '@/styles/globals.css'; import { RouteChangeListener } from './route-change-listener'; export const metadata: Metadata = { title: 'Coding Beauty', description: 'The official Coding Beauty home page.', icons: { icon: '/favicon.png', }, }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> {/* 👇 Persists with client-side navigation */} <RouteChangeListener /> <body>{children}</body> </html> ); }

useEffect needs 'use client'

Also, since server components are the default in Next.js 13, you’ll need to add the 'use client' directive at the top of the component file.

Otherwise, you won’t be able to use interactive client-side features like React hooks, including useEffect.

The useEffect React hook can't work without the 'use client' directive in Next.js.
useEffect can’t work without ‘use client’ in Next.js.

Listen for route/page change in Next.js pages directory

To handle a URL or location change in Next.js pages directory, combine the useEffect and the useRouter hooks:

src/pages/url-change-listener.tsx
import { useEffect } from 'react'; import { useRouter } from 'next/router'; export function UrlChangeListener() { const router = useRouter(); useEffect(() => { console.log(`The page is now: ${router.pathname}`); }, [router]); return <></>; }

Route change detector must persist with client-side routing

Just like with the app directory, the component that listens for the URL change with useEffect needs to be somewhere in the DOM where it will not get unmounted with client-side navigation.

In the pages directory, this could be the _app.tsx or _app.js file:

src/pages/_app.tsx
import '@/styles/globals.css'; import type { AppProps } from 'next/app'; import { UrlChangeListener } from './url-change-listener'; export default function App({ Component, pageProps }: AppProps) { return ( <> <UrlChangeListener /> <Component {...pageProps} /> </> ); }

Detect route/page with useRouter() events

Alternatively, we can detect a client-side URL change in the Next.js pages directory with events that the useRouter() object emits. For example:

src/pages/url-change-listener.tsx
import { useEffect } from 'react'; import { useRouter } from 'next/router'; export function UrlChangeListener() { const router = useRouter(); useEffect(() => { const startHandler = () => { console.log('Router change started'); }; const completeHandler = () => { console.log('Router change completed'); }; router.events.on('routeChangeStart', startHandler); router.events.on('routeChangeComplete', completeHandler); return () => { router.events.off('routeChangeStart', startHandler); router.events.off('routeChangeComplete', completeHandler); }; }, []); // 👇 You can put a progress bar or something here return <></>; }

Here we used two important router events:

  • routeChangeStart – fires when the route is about to change.
  • routerChangeComplete – fires when the router has changed completely.

There’s more too, and their names are just as self-explanatory as these two.

The `useRouter()` object has other properties apart from routeChangeStart and routeChangeComplete.

Key takeaways

  • To listen for a route or page change in Next.js app directory, combine the useEffect and usePathname hooks.
  • Detect a URL change in the pages directory with the useEffect and useRouter hooks.
  • You can also use the routeChangeStart and routeChangeComplete events from the useRouter() object to handle a location change.

How to quickly fix the “Cannot read property ‘then’ of undefined” error in JavaScript

The “Cannot read property ‘then’ of undefined” error occurs in JavaScript when you call try then() on a value – typically a function’s return value – but this value is undefined.

The "Cannot read property 'then' of undefined" error occurring in the VS Code terminal.
The “Cannot read property ‘then’ of undefined” error occurring in the VS Code terminal.

This could happen for one of the following reasons:

  1. Not returning the Promise in the function with return.
  2. Not returning a value in an async function.
  3. Not chaining Promises properly.

To fix it, ensure the function returns actually returns a Promise.

Now let’s look at some specific cases of the error and learn how to fix it in a few easy steps.

Fix: Promise not returned

The “Cannot read property ‘then’ of undefined” happens when you forget to use the return keyword return the Promise from the function.

JavaScript
function fetchData(apiUrl) { // 👇 `return` keyword missing fetch(apiUrl).then((response) => { return response.json(); }); } // ❌ Cannot read property 'then' of undefined fetchData('/api/data') .then((data) => console.log(data))

To fix it, simply return the Promise with return:

JavaScript
function fetchData(apiUrl) { // we return the Promise return fetch(apiUrl).then((response) => { return response.json(); }); } // ✅ Runs successfully fetchData('/api/data') .then((data) => console.log(data))

Fix: Asynchronous function doesn’t return a value

The “Cannot read property ‘then’ of undefined” error happens in JavaScript when an async function doesn’t return a value, for example, due to an oversight on our part when writing conditional control flow.

JavaScript
async function getUserData(userId) { if (userId) { const response = await fetch(`/api/users/${userId}`); return response.json(); } // 😕 No return if userId is absent } // ❌ Cannot read property 'then' of undefined if userId is absent getUserData().then(data => console.log(data));

To fix it, check all flow paths and make sure the async function always returns a value.

JavaScript
async function getUserData(userId) { if (userId) { const response = await fetch(`/api/users/${userId}`); return response.json(); } // 👍 Return a resolved Promise even if userId is absent return Promise.resolve(null); } // ✅ Now, we can safely use 'then' getUserData().then(data => console.log(data));

Fix: Promise is not properly chained

The “Cannot read property ‘then’ of undefined” error occurs in JavaScript when you don’t chain the Promises properly:

JavaScript
function fetchAndParseUser(apiUrl) { fetch(apiUrl) .then((response) => { console.log(response); // 😕 Forgot to return the 'json' Promise }); } // ❌ Error: Cannot read property 'then' of undefined fetchAndParseUser('/api/user') .then(data => console.log(data))

To fix it in this case, make sure that each then in the chain returns a Promise if we want to continue the chain.

JavaScript
function fetchAndParseUser(apiUrl) { // 👇 Here, we return the 'json' Promise return fetch(apiUrl) .then((response) => { console.log(response); return response.json(); // 👍 Return the Promise here }); } // ✅ Now, we can safely use 'then' fetchAndParseUser('/api/user') .then(data => console.log(data))

How to quickly detect the hover event in Vue.js

To detect mouse hover in Vue.js, use a hover state variable and the mouseenter & mouseleave events to detect when the mouse enters and leaves the element’s bounds.

App.vue
<template> <div @mouseenter="hover = true" @mouseleave="hover = false"></div> </template> <script> export default { data() { return { hover: false, }; }, }; </script>

The mouseenter event runs when the mouse pointer enters the bounds of an element, while mouseleave runs when it leaves.

We could also listen to the mouseover event to detect hover, this event runs for an element and every single one of its ancestor elements in the DOM tree (i.e. it bubbles) and this could cause serious performance problems in deep hierarchies. mouseenter doesn’t bubble so we can use it without worrying about this.

Change style on hover in Vue.js

To change the style of an element on hover in Vue.js, we can combine the hover state variable and the :class directive:

App.vue
<template> <div @mouseenter="hover = true" @mouseleave="hover = false" class="div" :class="{ 'div-hover': hover }" ></div> </template> <script> export default { data() { return { hover: false, }; }, }; </script> <style> .div { background-color: blue; width: 200px; height: 100px; } .div-hover { background-color: yellow; } </style>
Changing color on mouse hover in Vue.js.

Display another element on hover in Vue.js

We could also display another element in the UI when we detect hover. For example: a tooltip to display more info on a particular UI element.

To do this, you can pass the state variable to a v-if state directive you set on the element. This ensures that the element only displays when the hover state is true.

App.vue
<template> <button @mouseenter="hover = true" @mouseleave="hover = false" class="div" :class="{ 'div-hover': hover }" > Button </button> <p v-if="hover">Tooltip</p> </template> <script> export default { data() { return { hover: false, }; }, }; </script>
Displaying another element on hover.

Detect hover on Vue component

You can also use the mouseenter and mouseleave approach to detect hover on a custom Vue component.

components/StyledButton.vue
<template> <div> <button>Styled button</button> </div> </template> <style> button { height: 30px; width: 100px; background-color: lightgreen; } </style>
App.vue
<template> <styled-button @mouseenter="hover = true" @mouseleave="hover = false" ></styled-button> <p v-if="hover">Tooltip</p> </template> <script> import StyledButton from './components/StyledButton.vue'; export default { components: { StyledButton, }, data() { return { hover: false, }; }, }; </script>
Detecting mouse hover on a Vue.js custom component.

If you’re using Vue 2.x, you’ll need to use the .native event modifier to listen for native DOM events on the Vue component:

Vue
<styled-button @mouseenter.native="hover = true" @mouseleave.native="hover = false" ></styled-button>

Key takeaways

  • Detect mouse hover in Vue.js using a hover state variable and mouseenter & mouseleave events.
  • mouseover event can cause performance issues in deep hierarchies, mouseenter is safer.
  • Use hover state to conditionally apply CSS classes, changing element appearance on hover.
  • Use hover state with v-if to conditionally display elements like tooltips on hover.
  • mouseenter and mouseleave can detect hover on custom Vue components. Use .native modifier in Vue 2.x.

Quickly open Markdown link in new tab (target _blank)

To open a link in a new tab with target _blank in Markdown, use [link](url){:target="_blank"} or create an HTML <a> tag with target="blank".

But for GitHub README files, and on many other platforms that render Markdown, you simply can’t.

You can't open a link in a new tab in the GitHub README file Markdown editor.
You can’t open a link in a new tab in the GitHub README file Markdown editor.

What is Markdown?

Markdown is a lightweight markup language designed for simplicity and readability.

The original goal for Markdown was to enable people to write using an easy-to-read, easy-to-write plain text format, and optionally convert it to structurally valid HTML (or XHTML). In other words, Markdown is a text-to-HTML conversion tool for web writers. Many websites like Github.com, Notion.so, and Medium.com use Markdown in one way or another.

Opening a link in a new tab in Markdown can be beneficial for a number of reasons:

  1. User experience: By opening a link in a new tab, you keep your original page open, allowing the user to easily return to your content after they’ve finished going through the linked content.
  2. Decrease bounce rate: In web analytics, a bounce is when a user lands on your site and then leaves without any other interaction. When your site stays open in one tab while the linked content opens in another, it technically lowers your bounce rate which can be better for site metrics and SEO (Search Engine Optimization).
  3. Preserve context: If your content provides further reading or references via external links, opening these in a new tab helps to maintain the context for the user. The users will not have to go back and forth between pages to understand the context.
  1. Simplicity: Markdown was designed to be as simple as possible, and this does not provide support for attributes in its link syntax. The target="_blank" attribute required to open links in a new tab in HTML is absent from basic Markdown.
  2. Security: Links that open in a new tab can be a vector for a phishing attack known as “tabnapping“. If the linked site is malicious, it can potentially alter the content of the original page and trick users into providing sensitive information.
  3. User control: There’s an argument that it should be up to the user to decide whether they want a link to open in a new window or tab. This can be as simple as Ctrl + Click or a right-click in most browsers.

You can create a link that opens in a new tab in Markdown, create an HTML anchor <a> element, and set it’s target attribute to _blank, link in regular HTML:

Markdown
Visit <a href="https://codingbeautydev.com">Coding Beauty</a> for more educational and interesting content.

Alternatively, create a link that opens in a new tab in Markdown with the [link](url){:target="_blank"} syntax. For example:

Markdown
Visit [Coding Beauty](https://codingbeautydev.com) for articles on JavaScript, React, Next.js, and more.

This works in kramdown syntax and in the Jekyll framework.

Key takeaways

  • To open a link in a new tab in Markdown, use [link](url){:target="_blank"} or an HTML <a> tag with target="blank". However, not all platforms support this.
  • Markdown is a lightweight markup language for creating simple, readable text that can be converted to HTML.
  • Opening links in new tabs can enhance user experience, decrease bounce rates, and preserve context.
  • Many Markdown tools don’t support new tab links due to simplicity, security, and user control concerns.
  • Use an HTML <a> tag or [link](url){:target="_blank"} syntax to open links in new tabs.

One-liners: far more than just one line

Here’s one:

JavaScript
const groupBy = (arr, groupFn) => arr.reduce( (grouped, obj) => ({ ...grouped, [groupFn(obj)]: [...(grouped[groupFn(obj)] || []), obj], }), {} );

and another:

JavaScript
const randomHexColor = () => `#${Math.random().toString(16).slice(2, 8).padEnd(6, '0')}`; console.log(randomHexColor()); // #7a10ba (varies) console.log(randomHexColor()); // #65abdc (varies)

Indeed, they are impressive when done right; a nice way to show off language mastery.

But exactly is a one-liner? Is it really code that takes only one line? If so, then can’t every piece of code qualify as a one-liner, if we just remove all the newline characters? Think about it.

A minified JavaScript file with only one line of code.

It seems like we need a more rigorous definition of what qualifies as a one-liner. And, after a few minutes of thought when writing a previous article on one-liners, I came up with this:

A one-liner is a code solution to a problem, implemented with a single statement in a particular programming language, optionally using only first-party utilities.

Tari Ibaba (😎)

You can clearly see those particular keywords that set this definition apart from others

1. “…single statement…”

Single line or single statement? I go with the later.

Because the thing is, we squeeze every program ever made in a single line of code if we wanted; all the whitespace and file separation is only for us and our fellow developers.

If you’ve used Uglify.js or a similar minifier, you know what it does to all those pitiful lines of code; why it’s called Uglify.

Uglify changes this:

JavaScript
/** Obviously redudant comments here. Just meant to emphasize what Uglify does Class Person */ class Person { /** Constructor */ constructor(name, age) { this.name = name; this.age = age; } /** Print Message */ printMessage() { console.log(`Hello! My name is ${this.name} and I am ${this.age} years old.`); } } /** Creating Object */ var person = new Person('John Doe', 25); /** Printing Message */ person.printMessage();

to this:

JavaScript
class Person{constructor(e,n){this.name=e,this.age=n}printMessage(){console.log(`Hello! My name is ${this.name} and I am ${this.age} years old.`)}}var person=new Person("John Doe",25);person.printMessage();

Would you be impressed by someone who actually wrote code like in this minified way? I would say it’s just badly formatted code.

Would you call this a one-liner?

JavaScript
const sum = (a, b) => { const s1 = a * a; const s2 = b * b; return s1 + s2; }

Tools like the VS Code Prettier extension will easily split up those 3 statements into multiple lines:

The three statements are separated after VS Code Prettier auto-format on save.
The three statements are separated after VS Code Prettier auto-format on save.

A true one-liner way to get the sum of two squares would be something like this:

JavaScript
const sum = (a, b) => a * a + b * b;

One short, succinct statement does the same job with equal clarity.

On the other hand, what about code that spans multiple lines but only uses one statement? Like this:

JavaScript
const capitalizeWithoutSpaces = (str) => str .split('') .filter((char) => char.trim()) .map((char) => char.toUpperCase()) .join('');

I would say this function’s body is far more qualified to be a one-line than the two examples we saw above; single statement.

“…particular programming language”

We need this part because of abstraction.

int sum(int a, int b) { return a * a + b * b; }

This is a one-liner, isn’t it? Very harmless-looking and easy to understand.

How about now? :

sum(int, int): push rbp mov rbp, rsp mov DWORD PTR [rbp-4], edi mov DWORD PTR [rbp-8], esi mov eax, DWORD PTR [rbp-4] imul eax, eax mov edx, eax mov eax, DWORD PTR [rbp-8] imul eax, eax add eax, edx pop rbp ret

Wait, this is just another piece of code, right? Well, it is, except that they do exactly the same thing, but in different languages. One in C++, and the other in Assembly.

Now imagine how much more an equivalent machine language program will have. Clearly, we can only our sum() a one-liner function in the context of C++.

“…using only first-party utilities”

Once again, we need this part because of abstraction.

For us to consider a piece of code as a one-liner, it should only use built-in functions and methods that are part of the language’s standard library or core functionality. For example: array methods, the http module in Node.js, the os module in Python, and so on.

Without this, capitalizeWithoutSpaces() below would easily pass as a JavaScript one-liner:

JavaScript
// Not a one-liner const capitalizeWithoutSpaces = (str) => filter(str.split(''), (char) => char.trim()) .map((char) => char.toUpperCase()) .join(''); function filter(arr, callback) { // Look at all these lines const result = []; for (const item of arr) { if (callback(item)) { result.push(item); } } return result; }

filter could have contained many thousands of lines, yet capitalizeWithoutSpaces would still be given one-liner status.

It’s kind of controversial because a lot of these so-called first-party utilities are abstractions themselves with logic spanning dozens of lines. But just like the “single statement” specifier, it makes it impossible to have an unlimited number of one-liners.

Final thoughts

The essence of a one-liner in programming extends beyond the literal interpretation of its name. It lies not only in the minimalism of physical lines but also in the elegance and sophistication of its execution. It often requires a sound comprehension of the language at hand, an ability to concisely solve a problem, and the art of utilizing the language’s core functionalities with finesse.

A one-liner isn’t merely about squeezing code into a single line; It is where the clarity of thought, the elegance of language mastery, and the succinctness of execution converge. It’s the realm where brevity meets brilliance and the art of coding truly shines.

How to quickly redirect to another page URL in Next.js

To easily redirect to another page URL in Next.js:

  • Static redirect: use the redirects key in next.config.js.
  • In App Router server component: use redirect() from the next/navigation.
  • In pages Router: use useRouter() from next/navigation.
  • In middleware: use NextResponse.redirect() from next/server.
Redirecting to another page or URL in Next.js.
Redirecting to another page URL in Next.js.

To easily redirect to another page URL in Next.js, use the redirects key in your next.config.js file:

next.config.js
// ... const nextConfig = { // ... async redirects() { return [ { source: '/blog', destination: '/', permanent: true, }, ]; }, }; module.exports = nextConfig;

redirects is an async function that returns an array with items that have source, destination, and permanent properties:

  • source: A pattern matching the path of the incoming request.
  • destination: The path you want to redirect to.
  • permanent: If true, Next sets a 308 status code in the redirection response, which tells clients like search engines to cache the redirect from that point onwards. If false, Next will set a 307 status code (temporary redirect) instead, making sure that redirect doesn’t get cached.

Note: While 301 and 302 are the popular status codes for permanent and temporary redirects, Next.js uses 307 and 308 because many browsers changed the request method to GET for the destination URL, regardless of the method the client set in the original request to the source. So if POST v1/posts returned a 302 with v2/posts, the next request to v2/posts may end up GET v2/posts instead of POST v2/posts. But with 307 and 308, the browsers are guaranteed to preserve the request method.

Redirect statically with path parameter to page URL

You can use a path parameter like :path to match any path in the source URL and pass it on to the destination URL.

Here, /news/:slug/ will match /news/codingbeauty and redirect to /blog/codingbeauty.

next.config.js
// ... const nextConfig = { // ... async redirects() { return [ { source: '/news/:slug', destination: '/blog/:slug', permanent: true, }, ]; }, }; module.exports = nextConfig;

It doesn’t work for nested paths though.

Redirect statically with wildcard path to page URL

To match nested routes, query parameters, and anything else in the source path, use the * character after the path parameter.

next.config.js
// ... const nextConfig = { // ... async redirects() { return [ { source: '/articles/:slug*', destination: '/blog/:slug*', permanent: true, }, ]; }, }; module.exports = nextConfig;

Redirect to another page URL dynamically in Next.js App Router server component

To redirect to another page URL dynamically in a Next.js 13 server component, use the redirect() function from the next/navigation module:

JavaScript
import React from 'react'; import { Metadata } from 'next'; import { redirect } from 'next/navigation'; export const metadata: Metadata = { title: 'Next.js - Coding Beauty', description: 'Tutorials by Coding Beauty', }; export default function Page() { if (process.env.NEXT_PUBLIC_HOME) redirect('/home'); return <main>Welcome to Coding Beauty.</main>; }

The redirect() function redirects the browser to another URL; /home in this case.

Here this happens conditionally, but if the redirect is guaranteed to happen, it will surely prevent any JSX in the page from rendering, and editors like VS Code are smart enough to detect this:

Visual Studio Code can detect when the JSX in a page is unreachable due to a redirect.

Redirect to another page URL dynamically in App Router client component

To redirect to another page URL dynamically in a Next.js client component in the Next.js 13 app directory, use the push() method of the object returned by the useRouter hook from the next/navigation module:

Here we’re redirecting from from the /blog path to an external URL:

src/app/blog/page.tsx
'use client'; import React, { useEffect } from 'react'; import { useRouter } from 'next/navigation'; export default function Page() { const router = useRouter(); useEffect(() => { router.push('https://codingbeautydev.com'); }, []); return <main>Welcome to Coding Beauty.</main>; }

The useRouter hook returns a Router object that we use to programmatically change the route, resulting in a client-side redirect.

Redirect to another page URL dynamically in Pages Router component

It’s like in the App router, but useRouter comes from next/router instead of next/navigation.

To redirect to another page URL dynamically in a Next.js client component in the Pages router, use the push() method of the object returned by the useRouter hook from the next/router module.

src/pages/amazing-url.tsx
import Head from 'next/head'; import { useEffect } from 'react'; import { useRouter } from 'next/router'; export default function Home() { const router = useRouter(); useEffect(() => { router.push('/fantastic-url'); }, []); return ( <> <Head> <title>Amazing URL | Coding Beauty</title> <meta name="description" content="Generated by create next app" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.png" /> </Head> <main> <p>Coding Beauty</p> <h2>Amazing URL</h2> </main> </> ); }

The useRouter hook returns a Router object that we use to programmatically change the route to perform a redirect on the client side.

Next.js >= 12: Redirect to another page URL in middleware

To redirect to another page URL using middleware in Next.js 12.1 and above, use the NextResponse.redirect method from the next/server module in a middleware.ts file.

src/middleware.ts
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const url = request.nextUrl.clone(); if (url.pathname === '/old-blog') { url.pathname = '/new-blog'; return NextResponse.redirect(url); } }

Here we use request.nextUrl to get the current URL in the Next.js middleware.

You’ll place middleware.ts file in the same level as your pages directory – which could be the root directory, or src, if enabled.

middleware.ts is at the same level as the pages directory.
middleware.ts is at the same level as the pages directory.

The reason why this only works in Next.js 12.1 above is because starting from Next.js 12.1, relative URLs are not allowed in redirects with NextResponse.redirect() or NextResponse.rewrite().

Next.js 12: Redirect to another page URL in middleware

To redirect to another page URL dynamically using middleware in Next.js 12, use the NextResponse.redirect() method from the next/server module in a _middleware.js file inside the pages folder or a subfolder where you want the middleware to work:

pages/_middleware.js
import { NextResponse, NextRequest } from 'next/server'; export async function middleware(request, event) { const { pathname } = request.nextUrl; if (pathname == '/') { return NextResponse.redirect('/hello-nextjs'); } return NextResponse.next(); }

Key takeaways

  • In Next.js, there are several ways to quickly redirect to another page URL.
  • For static redirects, you can use the redirects key in the next.config.js file. It allows you to define source and destination paths with options for temporary or permanent redirects.
  • Path parameters (e.g., /blog/:slug) and wildcard paths (e.g., /blog/:slug*) can be used to match and redirect nested routes or query parameters.
  • In a Next.js App Router server component, you can use the redirect() function from the next/navigation module to dynamically redirect to another URL.
  • In the Next.js client component, you can use the push() method from the useRouter hook in the next/navigation (Next.js 13) or next/router (Next.js 12 and below) module to programmatically change the route and perform a client-side redirect.
  • For middleware in Next.js 12.1 and above, you can use the NextResponse.redirect() method from the next/server module in a middleware.ts file.
  • In Next.js 12 middleware, you can use the NextResponse.redirect() method in a _middleware.js file inside the pages folder or a subfolder.

Mojo: 7 brilliant Python upgrades in the new AI language

It is 35,000 times faster than Python. It is quicker than C. It is as easy as Python.

Enter Mojo: a newly released programming language made for AI developers and made by Modular, a company founded by Chris Lattner, the original creator of Swift.

This 35000x claim came from a benchmark comparison between Mojo and other languages, using the Mandelbrot algorithm on a particular AWS instance.
This 35000x claim came from a benchmark comparison between Mojo and other languages, using the Mandelbrot algorithm on a particular AWS instance.

It’s a superset of Python, combining Python’s usability, simplicity, and versatility with C’s incredible performance.

If you’re passionate about AI and already have a grasp on Python, then Mojo is definitely worth a try. So, let’s dive in and explore 7 powerful features of this exciting language together.

Mojo’s features

I signed up for Mojo access shortly after it was announced and got access a few days later.

I got access to the Mojo playground.

I started exploring all the cool new features they had to offer and even had the chance to run some code and see the language in action. Here are 7 interesting Python upgrades I found:

1. let and var declarations

Mojo introduces new let and var statements that let us create variables.

If we like we can specify a type like Int or String for the variable, as we do in TypeScript. var allows variables to change; let doesn’t. So it’s not like JavaScript’s let and var – There’s no hoisting for var and let is constant.

Mojo
def your_function(a, b): let c = a # Uncomment to see an error: # c = b # error: c is immutable if c != b: let d = b print(d) your_function(2, 3)

2. structs for faster abstraction

We have them in C++, Go, and more.

Structs are a Mojo feature similar to Python classes, but they’re different because Mojo classes are static: you can’t add more methods are runtime. This is a trade-off, as it’s less flexible, but faster.

Mojo
struct MyPair: var first: Int var second: Int # We use 'fn' instead of 'def' here - we'll explain that soon fn __init__(inout self, first: Int, second: Int): self.first = first self.second = second fn __lt__(self, rhs: MyPair) -> Bool: return self.first < rhs.first or (self.first == rhs.first and self.second < rhs.second)

Here’s one way struct is stricter than class: all fields must be explicitly defined:

Fields must be explicitly defined in Mojo structs.

3. Strong type checking

These structs don’t just give us flexibility, they let us check variable types at compile-time in Mojo, like the TypeScript compiler does.

Mojo
def pairTest() -> Bool: let p = MyPair(1, 2) # Uncomment to see an error: # return p < 4 # gives a compile time error return True

The 4 is an Int, the p is a MyPair; Mojo simply can’t allow this comparison.

4. Method overloading

C++, Java, Swift, etc. have these.

Function overloading is when there are multiple functions with the same name that accept parameters with different data types.

Look at this:

Mojo
struct Complex: var re: F32 var im: F32 fn __init__(inout self, x: F32): """Makes a complex number from a real number.""" self.re = x self.im = 0.0 fn __init__(inout self, r: F32, i: F32): """Makes a complex number from its real and imaginary parts.""" self.re = r self.im = i

Typeless languages like JavaScript and Python simply can’t have function overloads, for obvious reasons.

Although overloading is allowed in module/file functions and class methods based on parameter/type, it won’t work based on return type alone, and your function arguments need to have types. If don’t do this, overloading won’t work; all that’ll happen is the most recently defined function will overwrite all those previously defined functions with the same name.

5. Easy integration with Python modules

Having seamless Python support is Mojo’s biggest selling point by far.

And using Python modules in Mojo is straightforward. As a superset, all you need to do is call the Python.import_module() method, with the module name.

Here I’m importing numpy, one of the most popular Python libraries in the world.

Mojo
from PythonInterface import Python # Think of this as `import numpy as np` in Python let np = Python.import_module("numpy") # Now it's like you're using numpy in Python array = np.array([1, 2, 3]) print(array)

You can do the same for any Python module; the one limitation is that you have to import the whole module to access individual members.

All the Python modules will run 35,000 times faster in Mojo.

6. fn definitions

fn is basically def with stricter rules.

def is flexible, mutable, Python-friendly; fn is constant, stable, and Python-enriching. It’s like JavaScript’s strict mode, but just for def.

Mojo
struct MyPair: fn __init__(inout self, first: Int, second: Int): self.first = first self.second = second

fn‘s rules:

  • Immutable arguments: Arguments are immutable by default – including self – so you can’t mistakenly mutate them.
  • Required argument types: You have to specify types for its arguments.
  • Required variable declarations: You must declare local variables in the fn before using them (with let and var of course).
  • Explicit exception declaration: If the fn throws exceptions, you must explicitly indicate so – like we do in Java with the throws keyword.

7. Mutable and immutable function arguments

Pass-by-value vs pass-by-reference.

You may have across this concept in languages like C++.

Python’s def function uses pass-by-reference, just like in JavaScript; you can mutate objects passed as arguments inside the def. But Mojo’s def uses pass-by-value, so what you get inside a def is a copy of the passed object. So you can mutate that copy all you want; the changes won’t affect the main object.

Pass-by-reference improves memory efficiency as we don’t have to make a copy of the object for the function.

But what about the new fn function? Like Python’s def, it uses pass-by-reference by default, but a key difference is that those references are immutable. So we can read the original object in the function, but we can’t mutate it.

Immutable arguments

borrowed a fresh, new, redundant keyword in Mojo.

Because what borrowed does is to make arguments in a Mojo fn function immutable – which they are by default. This is invaluable when dealing with objects that take up a substantial amount of memory, or we’re not allowed to make a copy of the object we’re passing.

For example:

Mojo
fn use_something_big(borrowed a: SomethingBig, b: SomethingBig): """'a' and 'b' are both immutable, because 'borrowed' is the default.""" a.print_id() // 10 b.print_id() // 20 let a = SomethingBig(10) let b = SomethingBig(20) use_something_big(a, b)

Instead of making a copy of the huge SomethingBig object in the fn function, we simply pass a reference as an immutable argument.

Mutable arguments

If we want mutable arguments instead, we’ll use the new inout keyword instead:

Mojo
struct Car: var id_number: Int var color: String fn __init__(inout self, id: Int): self.id_number = id self.color = 'none' # self is passed by-reference for mutation as described above. fn set_color(inout self, color: String): self.color = color # Arguments like self are passed as borrowed by default. fn print_id(self): # Same as: fn print_id(borrowed self): print('Id: {0}, color: {1}') car = Car(11) car.set_color('red') # No error

self is immutable in fn functions, so we here we needed inout to modify the color field in set_color.

Key takeaways

  • Mojo: is a new AI programming language that has the speed of C, and the simplicity of Python.
  • let and var declarations: Mojo introduces let and var statements for creating optionally typed variables. var variables are mutable, let variables are not.
  • Structs: Mojo features static structs, similar to Python classes but faster due to their immutability.
  • Strong type checking: Mojo supports compile-time type checking, akin to TypeScript.
  • Method overloading: Mojo allows function overloading, where functions with the same name can accept different data types.
  • Python module integration: Mojo offers seamless Python support, running Python modules significantly faster.
  • fn definitions: The fn keyword in Mojo is a stricter version of Python’s def, requiring immutable arguments and explicit exception declaration.
  • Mutable and immutable arguments: Mojo introduces mutable (inout) and immutable (borrowed) function arguments.

Final thoughts

As we witness the unveiling of Mojo, it’s intriguing to think how this new AI-focused language might revolutionize the programming realm. Bridging the performance gap with the ease-of-use Python offers, and introducing powerful features like strong type checking, might herald a new era in AI development. Let’s embrace this shift with curiosity and eagerness to exploit the full potential of Mojo.