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.

How to Get the Value of a Form Input on Submit in React

To get the value of form input on submit in React:

  1. Create a state variable to store the value of the input.
  2. Set an onChange event handler on the input to update the state variable when the input’s value changes.
  3. Set an onSubmit event handler on the form element.
  4. Access the value of the input field in the onSubmit event handler.

For example:

App.js

import { useState } from 'react';

export default function App() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [message, setMessage] = useState('');

  const handleSubmit = (event) => {
    event.preventDefault();

    setMessage(`Hello ${firstName} ${lastName}!`);
    setFirstName('');
    setLastName('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        id="firstName"
        name="firstName"
        value={firstName}
        placeholder="First Name"
        onChange={(event) =>
          setFirstName(event.target.value)
        }
      />

      <br />
      <br />

      <input
        type="text"
        id="lastName"
        name="lastName"
        value={lastName}
        placeholder="Last Name"
        onChange={(event) => {
          setLastName(event.target.value);
        }}
      />

      <br />
      <br />

      <button type="submit">Submit</button>

      <br />
      <br />

      <h2>{message}</h2>
    </form>
  );
}
The input values are used to display a message on form submit.

With the useState() hook we create a state variable to store the current value of each input field. We also created another state variable (message) that will be updated with the values of the input fields after the user submits the form.

We set an inline onChange event handler on each input field to make the handler get called whenever the input field changes. In each handler, we use the target property of the Event object the handler receives to access the input element object. The value property of this object contains the input value, so we pass it to the state update function to update the value of that particular input.

The button element in the form has a type of submit, so every time the user clicks it, the submit event is triggered. We set an onSubmit event handler on the form to listen to this event. In this handler, we call preventDefault() on the Event object, to prevent the page from refreshing when the form is submitted.

To get the input values in this event handler after the form is submitted, we simply access the firstName and lastName state variables.

Get input value on form submit with event.target

We can also get the value of a form input on submit using the target property of the Event object. This is useful in cases where we don’t track the input value with a state variable, i.e., an uncontrolled input.

App.js

import { useState } from 'react';

export default function App() {
  const [message, setMessage] = useState('');

  const handleSubmit = (event) => {
    event.preventDefault();

    const firstName = event.target.firstName.value;
    const lastName = event.target.lastName.value;
    setMessage(`Hello ${firstName} ${lastName}!`);

    event.target.reset();
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        id="firstName"
        name="firstName"
        placeholder="First Name"
      />

      <br />
      <br />

      <input
        type="text"
        id="lastName"
        name="lastName"
        placeholder="Last Name"
      />

      <br />
      <br />

      <button type="submit">Submit</button>

      <br />
      <br />

      <h2>{message}</h2>
    </form>
  );
}

In an onSubmit event handler, the target property of the Event object returns the form element object (of type HTMLFormElement).

Because we set the name prop on the input element, the form element object will contain a property with the same name that returns the input element object, i.e., after setting name to firstName on an input element, we can access the element object for this input with the firstName property on the form element object.

After getting the value of each input and displaying the message, we call the reset() method on the form element object to restore the default values of the form’s input elements. This is how we clear the text in the input fields in this example since they’re not controlled by state variables.

Get input value on form submit with ref

We can also use a component ref to get the value of an uncontrolled form input on submit in React.

App.js

import { useState, useRef } from 'react';

export default function App() {
  const [message, setMessage] = useState('');
  const firstNameRef = useRef(undefined);
  const lastNameRef = useRef(undefined);

  const handleSubmit = (event) => {
    event.preventDefault();

    const firstName = firstNameRef.current.value;
    const lastName = lastNameRef.current.value;

    setMessage(`Hello ${firstName} ${lastName}!`);

    event.target.reset();
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        id="firstName"
        name="firstName"
        placeholder="First Name"
        ref={firstNameRef}
      />

      <br />
      <br />

      <input
        type="text"
        id="lastName"
        name="lastName"
        placeholder="Last Name"
        ref={lastNameRef}
      />

      <br />
      <br />

      <button type="submit">Submit</button>

      <br />
      <br />

      <h2>{message}</h2>
    </form>
  );
}

The data in a controlled input is handled by React state, but for an uncontrolled input, it is handled by the DOM itself. This is why the input in this example above doesn’t have a value prop or onChange event handler set. Instead, we access the input field’s value with a React ref. The DOM updates this value when the text in the input is changed.

For each input, we create a ref object with the useRef() hook and set it to the ref prop of the input. Doing this sets the current property of the ref object to the DOM object that represents the input element.

useRef() returns a mutable ref object that doesn’t change value when a component is updated. Also, modifying the value of this object’s current property doesn’t cause a re-render. This is in contrast to the setState update function returned from useState().

Although the React documentation recommends using controlled components, uncontrolled components offer some advantages. You might prefer them if the form is very simple and doesn’t need instant validation, and values only need to be accessed when the form is submitted.

How to Subtract 6 Months From a Date in JavaScript

To subtract 6 months from a date:

  1. Call the getMonth() method on the date to get the months.
  2. Subtract 6 from the return value of getMonth().
  3. Pass the result of the subtraction to the setMonth() method.

For example:

function subtract6Months(date) {
  date.setMonth(date.getMonth() - 6);

  return date;
}

// November 14, 2022
const date = new Date('2022-11-14T00:00:00.000Z');

const newDate = subtract6Months(date);

// May 14, 2022
console.log(newDate); // 2022-05-14T00:00:00.000Z

OurΒ subtract6Months()Β function takes aΒ DateΒ object and the number of months to subtract as arguments. It returns the sameΒ DateΒ object with 6 months subtracted.

The Date getMonth() returns a zero-based number that represents the month of a particular date.

The Date setMonth() method sets the months of a date to a specified zero-based number.

Note: β€œZero-based” here means that 0 is January, 1 is February, 2 is March, etc.

If the months subtracted would decrease the year of the date, setMonths() will automatically update the date information to reflect this.

// January 10, 2022
const date = new Date('2022-01-10T00:00:00.000Z');

date.setMonth(date.getMonth() - 6);

// July 10, 2021
console.log(date); // 2021-07-10T00:00:00.000Z

In this example, we subtract 6 months from a date in January 2022. This makes the year automatically get rolled back to 2021 by setMonth().

Avoid side-effects

The setMonth() mutates the Date object it is called on. This introduces a side effect into our subtract6Months() function. To avoid modifying the passed Date and create a pure function, make a copy of the Date and call setMonth() on this copy, instead of the original.

function subtract6Months(date) {
  // πŸ‘‡ Make copy with "Date" constructor
  const dateCopy = new Date(date);

  dateCopy.setMonth(dateCopy.getMonth() - 6);

  return dateCopy;
}

// August 13, 2022
const date = new Date('2022-08-13T00:00:00.000Z');

const newDate = subtract6Months(date);

// February 13, 2022
console.log(newDate); // 2022-02-13T00:00:00.000Z

// πŸ‘‡ Original not modified
console.log(date); // 2022-08-13T00:00:00.000Z

Tip: Functions that don’t modify external state (i.e., pure functions) tend to be more predictable and easier to reason about, as they always give the same output for a particular input. This makes it a good practice to limit the number of side effects in your code.

2. date-fns subMonths() function

Alternatively, we can use the subMonths() function from the date-fns NPM package to quickly subtract 6 months from a date. It works like our pure subtractMonths() function. subMonths() takes a Date object and the number of months to subtract as arguments. It returns a new Date object with the months subtracted.

import { subMonths } from 'date-fns';

// July 26, 2022
const date = new Date('2022-07-26T00:00:00.000Z');

const newDate = subMonths(date, 6);

// January 26, 2022
console.log(newDate); // 2022-01-26T00:00:00.000Z

// Original not modified
console.log(date); // 2022-07-26T00:00:00.000Z

Note that the subMonths() function returns a new Date object without modifying the one passed to it.

How to Subtract Months From a Date in JavaScript

1. Date getMonth() and setMonth() methods

To subtract months from a date in JavaScript:

  1. Call the getMonth() method on the Date to get the months.
  2. Subtract the months.
  3. Pass the result of the subtraction to the setMonth() method.

For example:

function subtractMonths(date, months) {
  date.setMonth(date.getMonth() - months);
  return date;
}

// August 13, 2022
const date = new Date('2022-08-13T00:00:00.000Z');

const newDate = subtractMonths(date, 3);

// May 13, 2022
console.log(newDate); // 2022-05-13T00:00:00.000Z

Our subtractMonths() function takes a Date object and the number of months to subtract as arguments. It returns the same Date object with the months subtracted.

The Date getMonth() method returns a zero-based number that represents the month of a particular date.

The Date setMonth() method sets the months of a date to a specified zero-based number.

Note: “Zero-based” here means that 0 is January, 1 is February, 2 is March, etc.

If the months subtracted would decrease the year of the date, setMonth() will automatically update the date information to reflect this.

// January 10, 2022
const date = new Date('2022-01-10T00:00:00.000Z');

date.setMonth(date.getMonth() - 2);

// November 10, 2021: year decreased by 1
console.log(date); // 2021-11-10T00:00:00.000Z

In this example, we subtracted 2 months from a date in January 2022. This makes setMonth() automatically roll the year back to 2021.

Avoid side-effects

The setMonth() method mutates the Date object it is called on. This introduces a side effect into our subtractMonths() function. To avoid modifying the passed Date and create a pure function, make a copy of the Date and call setMonth() on this copy, instead of the original.

function subtractMonths(date, months) {
  // πŸ‘‡ Make copy with "Date" constructor
  const dateCopy = new Date(date);

  dateCopy.setMonth(dateCopy.getMonth() - months);

  return dateCopy;
}

// August 13, 2022
const date = new Date('2022-08-13T00:00:00.000Z');

const newDate = subtractMonths(date, 3);

// May 13, 2022
console.log(newDate); // 2022-05-13T00:00:00.000Z

// πŸ‘‡ Original not modified
console.log(date); // 2022-08-13T00:00:00.000Z

Tip: Functions that don’t modify external state (i.e., pure functions) tend to be more predictable and easier to reason about, as they always give the same output for a particular input. This makes it a good practice to limit the number of side effects in your code.

2. date-fns subMonths() function

Alternatively, we can use the subMonths() function from the date-fns NPM package to quickly subtract months from a date. It works like our pure subtractMonths() function.

import { subMonths } from 'date-fns';

// June 27, 2022
const date = new Date('2022-06-27T00:00:00.000Z');

const newDate = subMonths(date, 4);

// February 27, 2022
console.log(newDate); // 2022-02-27T00:00:00.000Z

// πŸ‘‡ Original not modified
console.log(date); // 2022-06-27T00:00:00.000Z

How to Add a Class Conditionally in Vue

To add a class conditionally to an element in Vue, set the class prop to a JavaScript object where for each property, the key is the class name, and the value is the boolean condition that must be true for the class to be set on the element.

<p
  v-bind:class="{
    'class-1': class1,
    'class-2': class2,
  }"
>
  Coding
</p>

Here’s a complete example:

App.vue

<template>
  <div id="app">
    <input
      type="checkbox"
      name="class-1"
      v-model="class1"
    />
    <label for="class-1">Class 1</label>

    <br />

    <input
      type="checkbox"
      name="class-2"
      v-model="class2"
    />
    <label for="class-2">Class 2</label>

    <!-- πŸ‘‡ Add classes conditionally -->
    <p
      v-bind:class="{
        'class-1': class1,
        'class-2': class2,
      }"
    >
      Coding
    </p>
    <p
      v-bind:class="{
        'class-1': class1,
        'class-2': class2,
      }"
    >
      Beauty
    </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      class1: false,
      class2: false,
    };
  },
};
</script>

<style>
.class-1 {
  font-size: 2em;
  font-weight: bold;
}

.class-2 {
  color: blue;
  text-transform: uppercase;
}
</style>
Setting a class conditionally in Vue.
The classes are only applied when their respective checkboxes are checked.

The class-1 class will only present on the element if the class1 variable is true, and the class-2 class will only be present if the class2 variable is true. The values of these variables are determined by the current checked state of their respective checkboxes since we use v-model to set up a two-way binding between the variables and the checkboxes.

Use :class shorthand

We can use :class as a shorthand for v-bind:class.

<p
  :class="{
    'class-1': class1,
    'class-2': class2,
  }"
>
  Coding
</p>
<p
  :class="{
    'class-1': class1,
    'class-2': class2,
  }"
>
  Beauty
</p>

Pass object as computed property

The JavaScript object passed doesn’t have to be inline. It can be stored as a computed property in the Vue component instance.

<template>
  <div id="app">
    ...
    <!-- πŸ‘‡ Add classes conditionally -->
    <p :class="classObject">Coding</p>
    <p :class="classObject">Beauty</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      class1: false,
      class2: false,
    };
  },
  computed: {
    // πŸ‘‡ Computed object property
    classObject() {
      return {
        'class-1': this.class1,
        'class-2': this.class2,
      };
    },
  },
};
</script>
...

Add both static and dynamic classes

We can set the class prop on the same element twice, once to add static classes, and once to add the dynamic classes that will be present based on certain conditions.

For example:

<template>
  <div id="app">
    <input
      type="checkbox"
      name="class-1"
      v-model="class1"
    />
    <label for="class-1">Class 1</label>

    <br />

    <input
      type="checkbox"
      name="class-2"
      v-model="class2"
    />
    <label for="class-2">Class 2</label>

    <!-- πŸ‘‡ Add classes conditionally and statically -->
    <p
      class="static-1 static-2"
      :class="{ 'class-1': class1, 'class-2': class2 }"
    >
      Coding
    </p>
    <p
      class="static-1 static-2"
      :class="{ 'class-1': class1, 'class-2': class2 }"
    >
      Beauty
    </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      class1: false,
      class2: false,
    };
  },
};
</script>

<style>
.class-1 {
  font-size: 2em;
  font-weight: bold;
}

.class-2 {
  color: blue;
  text-transform: uppercase;
}

/* πŸ‘‡ Classes to add statically */
.static-1 {
  font-family: 'Segoe UI';
}

.static-2 {
  font-style: italic;
}
</style>
Adding a class conditionally in Vue.
The texts are styled with static classes before being conditionally styled with dynamic classes.

The static-1 and static-2 classes are always applied to the texts, making them italic and changing the font.

How to Convert XML to JSON in JavaScript

We can use the xml-js library from NPM to easily convert XML to JSON in JavaScript, i.e.:

import { xml2json } from 'xml-js';

// ...

const json = xml2json(xml);

For example:

index.js

import { xml2json } from 'xml-js';

const xml = `<name>Garage</name>
<cars>
    <color>red</color>
    <maxSpeed>120</maxSpeed>
    <age>2</age>
</cars>
<cars>
    <color>blue</color>
    <maxSpeed>100</maxSpeed>
    <age>3</age>
</cars>
<cars>
    <color>green</color>
    <maxSpeed>130</maxSpeed>
    <age>2</age>
</cars>`;

const json = xmltojson(xml, );

console.log(json);

This code will have the following output:

{"declaration":{"attributes":{"version":"1.0","encoding":"UTF-8"}},"elements":[{"type":"element","name":"languages","elements":[{"type":"element","name":"language","elements":[{"type":"text","text":"\n      English\n   "}]},{"type":"element","name":"language","elements":[{"type":"text","text":"\n      French\n   "}]},{"type":"element","name":"language","elements":[{"type":"text","text":"\n      Spanish\n   "}]}]}]}

What are XML and JSON?

Let’s go through these terms in case you’re not familiar with them

XML (eXtensible Markup Language) is a markup language similar to HTML that was designed to store and transport data. Unlike HTML, XML doesn’t have any predefined tags. Instead, we define our own tags according to our requirements.

JSON (JavaScript Object Notation) is a text format used to store and transport data based on JavaScript object syntax and is commonly used to build RESTful APIs.

Install xml-js

Before using xml-js, we need to install it in our project. We can do this with the NPM CLI.

npm i xml-js

Or with the Yarn CLI:

yarn add xml-js

After installation, we’ll be able to import it into a JavaScript module, like this:

import { xml2json } from 'xml-js';

We use import destructuring to access the xml2json() method directly from the library.

For a CommonJS module, we’ll import it like this instead:

const { xml2json } = require('xml-js');

The xml2json() function

The xml2json() function has two parameters. The first is the XML string to convert to JSON, and the second is an optional object.

const json = xml2json(xml, { spaces: 2, compact: true });

Customize conversion of XML to JSON

We use this object to specify various options for customizing the conversion process.

In our example, we don’t pass an object, so the default options are used. We can pass an object with a spaces option to properly format and indent the resulting JSON.

import { xml2json } from 'xml-js';

const xml = `<?xml version="1.0" encoding="UTF-8"?>
<languages>
   <language>
      English
   </language>
   <language>
      French
   </language>
   <language>
      Spanish
   </language>
</languages>`;

// πŸ‘‡ Set "spaces" option
const json = xml2json(xml, { spaces: 2 });

console.log(json);

Here’s the new output:

{
  "declaration": {
    "attributes": {
      "version": "1.0",
      "encoding": "UTF-8"
    }
  },
  "elements": [
    {
      "type": "element",
      "name": "languages",
      "elements": [
        {
          "type": "element",
          "name": "language",
          "elements": [
            {
              "type": "text",
              "text": "\n      English\n   "
            }
          ]
        },
        {
          "type": "element",
          "name": "language",
          "elements": [
            {
              "type": "text",
              "text": "\n      French\n   "
            }
          ]
        },
        {
          "type": "element",
          "name": "language",
          "elements": [
            {
              "type": "text",
              "text": "\n      Spanish\n   "
            }
          ]
        }
      ]
    }
  ]
}

The compact property to determine whether the resulting JSON should be detailed or compact. It is false by default.

import { xml2json } from 'xml-js';

const xml = `<?xml version="1.0" encoding="UTF-8"?>
<languages>
   <language>
      English
   </language>
   <language>
      French
   </language>
   <language>
      Spanish
   </language>
</languages>`;

// πŸ‘‡ Set "compact" option
const json = xml2json(xml, { spaces: 2, compact: true });

console.log(json);

Now the resulting JSON will have a significantly smaller size than before:

{
  "_declaration": {
    "_attributes": {
      "version": "1.0",
      "encoding": "UTF-8"
    }
  },
  "languages": {
    "language": [
      {
        "_text": "\n      English\n   "
      },
      {
        "_text": "\n      French\n   "
      },
      {
        "_text": "\n      Spanish\n   "
      }
    ]
  }
}

How to Detect a Browser or Tab Close Event in JavaScript

To detect a browser or tab close event in JavaScript:

  1. Add a beforeunload event listener to the global window object.
  2. In this listener, call the preventDefault() method on the Event object passed to it.
  3. Set the returnValue property of this Event object to an empty string ('').

JavaScript

window.addEventListener('beforeunload', (event) => {
  event.preventDefault();
  event.returnValue = '';
});

We’ll need to detect the event of a browser or tab close to alert the user of any unsaved changes on the webpage.

We use the addEventListener() method to attach an event handler to any DOM objects such as HTML elements, the HTML document, or the window object.

The beforeunload event is fired right before the window, document, and its resources are about to be unloaded. At this point, the document is still visible, and the event is still cancelable.

We have to call preventDefault() on the Event object the handler receives to show the confirmation dialog in the browser. The preventDefault() method prevents the default action of an event. For the beforeunload event, preventDefault() stops the unloading of the resources, the window, and the document.

This JavaScript code will cause a confirmation dialog to be displayed when the user tries to close the browser or tab. Here’s some sample HTML to use the JavaScript in.

HTML

<a href="wp.codingbeautydev.com">Coding Beauty</a>
<br />
Try to close the tab or browser
A confirmation dialog is shown when the user tries to close the tab.
A confirmation dialog is shown when the user tries to close the tab.
A confirmation dialog is shown when the user tries to close the browser.
A confirmation dialog is shown when the user tries to close the browser.

Note that the beforeunload event is triggered not only when the browser or tab is closed, but also when a link is clicked, a form is submitted, the backward/forward button is pressed, or the page is refreshed.

A confirmation dialog is shown when a link is clicked on the page.
A confirmation dialog is shown when a link is clicked on the page.
A confirmation dialog is shown when the user tries to refresh the page.
A confirmation dialog is shown when the user tries to refresh the page.

How to Get an Input Value On Change in React

To get the value of an input on change in React, set an onChange event handler on the input, then use the target.value property of the Event object passed to the handler to get the input value.

For example:

App.js

import { useState } from 'react';

export default function App() {
  const [message, setMessage] = useState('');

  const handleChange = (event) => {
    // πŸ‘‡ Get input value from "event"
    setMessage(event.target.value);
  };

  return (
    <div>
      <input
        type="text"
        id="message"
        name="message"
        onChange={handleChange}
      />

      <h2>Message: {message}</h2>
    </div>
  );
}
Getting an input value on change in React.
The message is updated when the input value changes.

Here we create a state variable (message) to track the input value. By setting an onChange event handler, the handler function will get called whenever the text in the input field changes.

The handler function will be called with an Event object containing data and allowing actions related to the event. The target property of this object lets us access the object representing the input element.

This input element object has a value property which returns the text currently in the input field. So we call the setMessage() function with this property as an argument to update the message variable with the input value, and this reflects on the page after the DOM is updated.

The Complete Guide to JavaScript Symbols

In this article, we’re going to learn all about JavaScript symbols and how to use them in practice.

Creating symbols

The Symbol is a data type that was added to JavaScript in ES6. To create a symbol, you can use the global Symbol() function.

const sym1 = Symbol();

The Symbol() function accepts an optional string that serves as the description of the symbol.

const sym2 = Symbol('color');

const sym3 = Symbol('maxSpeed');

const sym4 = Symbol('age');

You can access this description with the description property of the Symbol. If no description was set, this property would be undefined.

console.log(sym1.description); // undefined

console.log(sym2.description); // color

console.log(sym3.description); // maxSpeed

console.log(sym4.description); // age

Every time you call the Symbol() function, it returns a new unique value.

console.log(Symbol() === Symbol()); // false

console.log(Symbol('color') === Symbol('color')); // false

console.log(Symbol('age') === Symbol('age')); // false

As a symbol is a primitive value, using the typeof operator on it will return a symbol string.

console.log(typeof sym1); // symbol

Trying to use the new operator to create a symbol will cause a TypeError. This prevents the creation of an explicit Symbol wrapper object in place of a new symbol value.

const num1 = Number(2);
console.log(typeof num1); // number

const num2 = new Number();
console.log(typeof num2); // object

const bool1 = Boolean(true);
console.log(typeof bool1); // boolean

const bool2 = new Boolean(true);
console.log(typeof bool2); // object

const sym1 = Symbol('color');
console.log(typeof sym1); // symbol

// ❌ TypeError
const sym2 = new Symbol('color');
console.log(typeof sym2);

Sharing symbols

ES6 comes with a global symbol registry that allows you to share symbols globally. To create a symbol for sharing, you use the Symbol.for() method instead of the Symbol() function. Symbol.for() takes a key and returns a symbol value from the registry.

For example:

const color = Symbol.for('color');

The Symbol.for() method first searches the global symbol registry for a symbol with the color key and returns it if it exists. Otherwise, Symbol.for() creates a new symbol, adds it to the registry with the specified color key, and returns the symbol.

Afterward, calling Symbol.for() with the same key returns this same symbol, as it now exists in the registry.

const carColor = Symbol.for('color');

console.log(color === carColor); // true

The Symbol.keyFor() method works together with Symbol.for() to retrieve information from the global symbol registry. You can get the key of a symbol that exists in the registry with Symbol.keyFor().

console.log(Symbol.keyFor(carColor)); // 'color'

If the key doesn’t exist, Symbol.keyFor() returns undefined.

const filename = Symbol('filename');

console.log(Symbol.keyFor(filename)); // undefined;

Uses of symbols

1. Enumerations

With symbols, we can define a group of constants to set a variable to a predefined set of values – an enumeration.

Let’s say we want to create a variable to store a value representing the day of the week. To create an enum, we could use strings like ‘Mon', 'Tue', 'Wed', etc.

const daysOfWeek = {
  Mon: 'Mon',
  Tue: 'Tue',
  Wed: 'Wed',
  Thu: 'Thu',
  Fri: 'Fri',
  Sat: 'Sat',
  Sun: 'Sun',
};

const dayOfWeek = daysOfWeek.Tue;

By using symbols in place of the string, we can ensure that the constants are always distinct from one another, and not worry about duplicate string values.

const daysOfWeek = {
  Mon: Symbol('Mon'),
  Tue: Symbol('Tue'),
  Wed: Symbol('Wed'),
  Thu: Symbol('Thu'),
  Fri: Symbol('Fri'),
  Sat: Symbol('Sat'),
  Sun: Symbol('Sun'),
};

const dayOfWeek = daysOfWeek.Tue;

2. Prevention of property name clashes

Since a symbol is always guaranteed to be unique, we can use it to avoid name clashes when entirely separate sections of code need to store data on an object. The different sections may not be aware of each other, but each wants to make sure that the property it uses is not going to be used by another.

For example:

// module-1.js

const module1 = () => {
  // Prevent external access with closure
  const module1Sym = Symbol();
  return (obj) => {
    // Put some data on obj that this module can access later
    obj[module1Sym] = 'module 1 data';
  };
};

// module-2.js

const module2 = () => {
  // Prevent external access with closure
  const module2Sym = Symbol();
  return (obj) => {
    // Put some data on obj that this module can access later
    obj[module2Sym] = 'module 2 data';
  };
};

// index.js

const obj = {};
module1(obj);
module2(obj);

If the modules each used a string instead of a symbol for the property name, it could cause problems as the names could clash.

For example:

// module-1.js

const module1 = () => {
  return (obj) => {
    // Put some data on obj that this module can access later
    obj.moduleData = 'module 1 data';
  };
};

// module-2.js

const module2 = () => {
  return (obj) => {
    // Put some data on obj that this module can access later
    obj.moduleData = 'module 2 data';
  };
};

// index.js

const obj = {};
module1(obj);
module2(obj);

// 'module 1 data' has been overwritten by the call the module2()

Note that symbol properties are not enumerable, if you call Object.keys() on the object, they will not be included in the array result.

const dayOfWeek = Symbol('dayOfWeek');
const event = {
  [dayOfWeek]: daysOfWeek.Tue,
  description: 'Birthday',
};

console.log(Object.keys(event)); // [ 'description' ]

Object.getOwnPropertyNames() returns an array of all the enumerable and non-enumerable properties of an object, but symbol properties are still excluded.

console.log(Object.getOwnPropertyNames(event)); // [ 'description' ]

To get all the symbol properties, you use the Object.getOwnPropertySymbols() method. This method returns an array of all symbol properties found directly on a given object.

console.log(Object.getOwnPropertySymbols(event)); // [ Symbol(dayOfWeek) ]

Well-known symbols

The Symbol class comes with static properties known as well-known symbols. They are used to implement and customize the behavior of certain built-in JavaScript operations.

Let’s explore some of these well-known symbols:

Symbol.hasInstance

The Symbol.hasInstance method customizes the behavior of the instanceof operator. Generally, when you use the instanceof operator like this:

obj instanceof type

JavaScript will call the Symbol.instanceof method like this:

type[Symbol.hasInstance](obj);

Here’s an example where we use the instanceof method on an instance of a user-defined class.

class List {}

console.log([] instanceof List); // false

Going by the default behavior of instanceof, [] is an Array, not an instance of the List class, so instanceof returns false here.

If we want to change this behavior and make instanceof return true in this scenario, we would customize the Symbol.hasInstance method like this:

class List {
  static [Symbol.hasInstance](obj) {
    return Array.isArray(obj);
  }
}

console.log([] instanceof List); // true

Symbol.iterator

With the Symbol.iterator method, we can specify if and how objects of a class can be iterated over. When this method is present, we will be able to use the for...of loop and spread syntax (...) on the objects of the class.

When you use the for...of loop on an array:

const numbers = [1, 2, 3];

for (const num of numbers) {
  console.log(num);
}

/*
1
2
3
*/

Internally, JavaScript first calls the Symbol.iterator method of the numbers array to get the iterator object. Then it continuously calls the next() method on the iterator object and copies the value property in the num variable, It exits the loop when the done property of the iterator object is true.

var iterator = numbers[Symbol.iterator]();

console.log(iterator.next()); // Object {value: 1, done: false}

console.log(iterator.next()); // Object {value: 2, done: false}

console.log(iterator.next()); // Object {value: 3, done: false}

console.log(iterator.next()); // Object {value: undefined, done: true}

By default, objects of a user-defined class are not iterable. But we can make them iterable with the Symbol.iterator method, as you’ll use in the following example:

class List {
  elements = [];

  add(element) {
    this.elements.push(element);
    return this;
  }

  // Generator
  *[Symbol.iterator]() {
    for (const element of this.elements) {
      yield element;
    }
  }
}

const colors = new List();
colors.add('red').add('blue').add('yellow');

// Works because of Symbol.iterator
for (const color of colors) {
  console.log(color);
}

/*
red
blue
yellow
*/

console.log([...colors]);
// [ 'red', 'blue', 'yellow' ]

Symbol.toStringTag

This symbol lets us customize the default string description of the object. It is used internally by the Object.prototype.toString() method.

class CarClass {
  constructor(color, maxSpeed, age) {
    this.color = color;
    this.maxSpeed = maxSpeed;
    this.age = age;
  }
}

const car = new CarClass('red', 100, 2);

console.log(Object.prototype.toString.call(car));
// [object Object]

Here is the default implementation of Symbol.toStringTag was outputted. Here’s how we can customize it:

class CarClass {
  constructor(color, maxSpeed, age) {
    this.color = color;
    this.maxSpeed = maxSpeed;
    this.age = age;
  }

  get [Symbol.toStringTag]() {
    return 'Car';
  }
}

const car = new CarClass('red', 100, 2);

console.log(Object.prototype.toString.call(car));
// [object Car]

Symbol.toPrimitive

The Symbol.toPrimitive method makes it possible for an object to be converted to a primitive value. It takes a hint argument that specifies the type of the resulting primitive value. This hint argument can be one of 'number', 'string', or 'default'.

Here’s an example of using the Symbol.toPrimitive method.

class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  [Symbol.toPrimitive](hint) {
    if (hint === 'string') {
      return `${this.amount} ${this.currency}`;
    } else if (hint === 'number') {
      return this.amount;
    } else if (hint === 'default') {
      return `${this.amount} ${this.currency}`;
    }
  }
}

const price = new Money(500, 'USD');

console.log(String(price)); // 500 USD
console.log(+price); // 500
console.log('Price is ' + price); // Price is 500 USD

Conclusion

In this article, we learned how to create JavaScript symbols and share them in the global symbol registry. We saw the advantages of using symbols, and how well-known symbols can be used to customize the behavior of built-in JavaScript operations.

How to Get a File Name Without the Extension in Node.js

To get the name of a file without the extension in Node.js, use the parse() method from the path module to get an object representing the path. The name property of this object will contain the file name without the extension.

For example:

const path = require('path');

path.parse('index.html').name; // index

path.parse('package.json').name; // package

path.parse('image.png').name; // image

The parse() method

The parse() method returns an object with properties that represent the major parts of the given path. The object it returns has the following properties:

  1. dir – the directory of the path.
  2. root – the topmost directory in the operating system.
  3. base – the last portion of the path.
  4. ext – the extension of the file.
  5. name – the name of the file without the extension.
path.parse('C://Code/my-website/index.html');

/*
Returns:
{
  root: 'C:/',
  dir: 'C://Code/my-website',
  base: 'index.html',
  ext: '.html',
  name: 'index'
}
*/

If the path is not a string, parse() throws a TypeError.

// ❌ TypeError: Received type of number instead of string
path.parse(123).name;

// ❌ TypeError: Received type of boolean instead of string
path.parse(false).name;

// ❌ TypeError: Received type of URL instead of string
path.parse(new URL('https://example.com/file.txt')).name;

// βœ… Received correct type of string
path.parse('index.html').name; // index

How to Get a File Extension in Node.js

To get the extension of a file in Node.js, we can use the extname() method from the path module.

For example:

const path = require('path');

path.extname('style.css') // .css

path.extname('image.png') // .png

path.extname('prettier.config.js') // .js

The extname() method

The extname() method returns the extension of the given path from the last occurrence of the . (period) character to the end of the string in the last part of the path.

If there is no . in the last part of the path, or if the path starts with . and it is the only . character in the path, extname() returns an empty string.

path.extname('index.'); // .

path.extname('index'); // '' (empty string)

path.extname('.index');   // '' (empty string)

path.extname('.index.html'); // .html

If the path is not a string, extname() throws a TypeError.

const path = require('path');

// ❌ TypeError: Received type number instead of string
path.extname(123);

// ❌ TypeError: Received type boolean instead of string
path.extname(false);

// ❌ TypeError: Received URL instance instead of string
path.extname(new URL('https://example.com/file.txt'));

// βœ… Received type of string
path.extname('package.json'); // .json