Categories
Vue

How to Use Environment Variables in Your Vue.js App

Vue CLI makes using them in your app easy by allowing variables in an .env file that have keys starting with VUE_APP to be used in your app.

Environment variables are often used for things that do not belong in your code like API keys and URLs. Vue CLI makes using them in your app easy by allowing variables in an .env file that have keys starting with VUE_APP to be used in your app.

The variables can be accessed by using the process.env object. For example, if you have VUE_APP_API_KEY in your .env file, then you can access it by using process.env.VUE_APP_API_KEY .

You can also have an .env file for other environments by adding an extension to the .env file. For example, you can use .env.staging for the staging environment if you want to include your staging URLs when you want to deploy to your staging server.

In this article, we will build a photo app that allows users to search for images and display images in a masonry grid. The image grid will have infinite scroll to get more images. We will use the vue-masonry library for render the image grid, and vue-infinite-scroll for the infinite scrolling effect.

To implement the masonry effect, we have to set the width of the image proportional to the screen width and set the image height to be proportional to the aspect ratio of the image.

This is a pain to do if it’s done without any libraries, so people have made libraries to create this effect.

Our app will display images from the Pixabay API. You can view the API documentation and register for a key at https://pixabay.com/api/docs/

We will store the API key in the .env file on the project’s root folder.

Once we have the Pixabay API key, we can start writing our app. To start, we create a project called photo-app . To do this, run:

npx @vue/cli create photo-app

This will create the files for our app and install the packages for the built-in libraries. We choose ‘manually select features’ and choose Babel, Vue Router and CSS Preprocessor.

Next, we install our own packages. We need the vue-masonry library and vue-infinite-scroll we mentioned above. In addition, we need BootstrapVue for styling, Axios for making HTTP requests and Vee-Validate for form validation.

We install all the packages by running:

npm i axios bootstrap-vue vee-validate vue-infinite-scroll vue-masonry

With all the packages installed, we can start writing our app. Create a mixins folder in the src folder and create a requestsMixin.js file.

Then we add the following to the file:

const axios = require("axios");
const APIURL = "https://pixabay.com/api";

export const requestsMixin = {
  methods: {
    getImages(page = 1) {
      return axios.get(`${APIURL}/?page=${page}&key=${process.env.VUE_APP_API_KEY}`);
    },

    searchImages(keyword, page = 1) {
      return axios.get(
        `${APIURL}/?page=${page}&key=${process.env.VUE_APP_API_KEY}&q=${keyword}`
      );
    }
  }
};

We call the endpoints to search for images here. process.env.VUE_APP_API_KEY is retrieved from the .env file in the root folder of our project. Note that the environment variables we use have to have keys that begin with VUE_APP .

Next, in Home.vue , replace the existing code with:

<template>
  <div class="page">
    <h1 class="text-center">Home</h1>
    <div
      v-infinite-scroll="getImagesByPage"
      infinite-scroll-disabled="busy"
      infinite-scroll-distance="10"
    >
      <div
        v-masonry="containerId"
        transition-duration="0.3s"
        item-selector=".item"
        gutter="5"
        fit-width="true"
        class="masonry-container"
      >
        <div>
          <img
            :src="item.previewURL"
            v-masonry-tile
            class="item"
            v-for="(item, index) in images"
            :key="index"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { requestsMixin } from "../mixins/requestsMixin";

export default {
  name: "home",
  mixins: [requestsMixin],
  data() {
    return {
      images: [],
      page: 1,
      containerId: null
    };
  },
  methods: {
    async getImagesByPage() {
      const response = await this.getImages(this.page);
      this.images = this.images.concat(response.data.hits);
      this.page++;
    }
  },
  beforeMount() {
    this.getImagesByPage();
  }
};
</script>

We use the vue-infinite-scroll and vue-masonry packages here. Note that we specified the transition-duration to tweak the transition from showing nothing to showing the images, fit-width makes the columns fit the container. gutter specifies the width of the space between each column in pixels. We also set a CSS class name in the v-masonry container to change the styles later.

Inside the v-masonry div, we loop through the images, we set the v-masonry-tile to indicate that it is tile so that it will resize them to a masonry grid.

In the script object, we get the images when the page loads with the beforeMount hook. Since we are adding infinite scrolling, we keep adding images to the array as the user scrolls down. We call getImagesByPage as the user scrolls down as indicated by the v-infinite-scroll prop. We set infinite-scroll-disabled to busy to set disable scrolling if busy is set to true . infinite-scroll-distance indicates the distance from the bottom of the page in percent for scrolling to be triggered.

Next create ImageSearchPage.vue in the views folder and add:

<template>
  <div class="page">
    <h1 class="text-center">Image Search</h1>
    <ValidationObserver ref="observer" v-slot="{ invalid }">
      <b-form @submit.prevent="onSubmit" novalidate>
        <b-form-group label="Keyword" label-for="keyword">
          <ValidationProvider name="keyword" rules="required" v-slot="{ errors }">
            <b-form-input
              :state="errors.length == 0"
              v-model="form.keyword"
              type="text"
              required
              placeholder="Keyword"
              name="keyword"
            ></b-form-input>
            <b-form-invalid-feedback :state="errors.length == 0">Keyword is required</b-form-invalid-feedback>
          </ValidationProvider>
        </b-form-group>

        <b-button type="submit" variant="primary">Search</b-button>
      </b-form>
    </ValidationObserver>

    <br />

    <div
      v-infinite-scroll="searchAllImages"
      infinite-scroll-disabled="busy"
      infinite-scroll-distance="10"
    >
      <div
        v-masonry="containerId"
        transition-duration="0.3s"
        item-selector=".item"
        gutter="5"
        fit-width="true"
        class="masonry-container"
      >
        <div>
          <img
            :src="item.previewURL"
            v-masonry-tile
            class="item"
            v-for="(item, index) in images"
            :key="index"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { requestsMixin } from "../mixins/requestsMixin";

export default {
  mixins: [requestsMixin],
  data() {
    return {
      form: {},
      page: 1,
      containerId: null,
      images: []
    };
  },
  methods: {
    async onSubmit() {
      const isValid = await this.$refs.observer.validate();
      if (!isValid) {
        return;
      }
      this.page = 1;
      await this.searchAllImages();
    },

    async searchAllImages() {
      if (!this.form.keyword) {
        return;
      }
      const response = await this.searchImages(this.form.keyword, this.page);
      if (this.page == 1) {
        this.images = response.data.hits;
      } else {
        this.images = this.images.concat(response.data.hits);
      }
      this.page++;
    }
  }
};
</script>

The infinite scrolling and masonry layout are almost the same, except when the keyword changes, we reassign the this.images array to the new items instead of keep adding them to the existing array so that users see the new results.

The form is wrapped inside the ValidationObserver so that we can get the validation status of the whole form inside the ValidationObserver . In the form, we wrap the input with ValidationProvider so that the form field can be validated and a validation error message displayed for the input. We check if keyword is filled in.

Once the user clicks Search, onSubmit is run, which runs await this.$refs.observer.validate(); to get the form validation status. If that results to true , then searchAllImages will be run to get the images.

Next, we replace the existing code in App.vue with:

<template>
  <div>
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand href="#">Photo App</b-navbar-brand>

      <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>

      <b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <b-nav-item to="/" :active="path == '/'">Home</b-nav-item>
          <b-nav-item to="/imagesearch" :active="path == '/imagesearch'">Image Search</b-nav-item>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
    <router-view />
  </div>
</template>

<script>
export default {
  data() {
    return {
      path: this.$route && this.$route.path
    };
  },
  watch: {
    $route(route) {
      this.path = route.path;
    }
  }
};
</script>

<style lang="scss">
.page {
  padding: 20px;
}

.item {
  width: 30vw;
}

.masonry-container {
  margin: 0 auto;
}
</style>

We add the BootstrapVue b-navbar here to display a top bar with links to our pages. In the script section, we watch the current route by getting this.$route.path . We set the active prop by check the path against our watched path to highlight the links.

In the style section, we set the padding of our pages with the page class, we set the photo width with the item class as indicated in the item-selector of our v-masonry div, and we set the masonry-container ‘s margin to 0 auto so that it will be centered in the page.

Next in main.js , replace the existing code with:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import BootstrapVue from "bootstrap-vue";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import { required } from "vee-validate/dist/rules";
import { VueMasonryPlugin } from "vue-masonry";
import infiniteScroll from "vue-infinite-scroll";

Vue.config.productionTip = false;

extend("required", required);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
Vue.use(VueMasonryPlugin);
Vue.use(infiniteScroll);
Vue.use(BootstrapVue);

new Vue({
  router,
  render: h => h(App)
}).$mount("#app");

to add all the libraries we used in the components and the Vee-Validate validation rules that we used. Also, we import our Bootstrap styles here so that we see the styles everywhere.

Next in router.js , replace the existing code with:

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import ImageSearchPage from "./views/ImageSearchPage.vue";

Vue.use(Router);

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home
    },
    {
      path: "/imagesearch",
      name: "imagesearch",
      component: ImageSearchPage
    }
  ]
});

to add our routes.

Finally, in index.html , we replace the existing code with:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title>Photo App</title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but vue-masonry-tutorial-app doesn't work properly without
        JavaScript enabled. Please enable it to continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

to rename the title of our app.

Categories
React

How to Handle Input Value Changes in React

We will make a simple calendar app where users can drag over a date range and add a calendar entry.

A special feature of React is that you have to handle input value changes yourself. Otherwise, users cannot see what they entered as the value is not set in the state.

To update the input value and set it in the state of our component, first we have to add:

const [title, setTitle] = React.useState("");

to create the function setTitle to set the value of title .

Then we have added a handler function to get the value from the input and set it:

const handleTitleChange = ev => setTitle(ev.target.value);

ev.target.value has the input’s value.

Then when we add the input, we have to add it like this:

<input
 type="text"
 name="title"
 placeholder="Title"
 value={title || ""}
 onChange={handleTitleChange}
 isInvalid={!title}
/>

We pass in the handleTitleChange function to the onChange prop so that title will be set. Then once it’s set then value prop will be populated and users will be able to see the value they entered.

In this article, we will make a simple calendar app where users can drag over a date range and add a calendar entry. Users can also click on an existing calendar entry and edit the entry. Existing entries can also be deleted. The form for adding and editing the calendar entry will have a date and time pickers to select the date and time.

React has many calendar widgets that we can add to our apps. One of them is React Big Calendar. It has a lot of features. It has a month, week, and daily calendar. Also, you can navigate easily to today or any other days with back and next buttons. You can also drag over a date range in the calendar to select the date range. With that, you can do any manipulation you want with the dates.

We will save the data on the back end in a JSON file.

We will use React to build our app. To start, we run:

npx create-react-app calendar-app

to create the project.

Next, we have to install a few packages. We will use Axios for HTTP requests to our back end, Bootstrap for styling, MobX for simple state management, React Big Calendar for our calendar component, React Datepicker for the date and time picker in our form, and React Router for routing.

To install them, we run:

npm i axios bootstrap mobx mobx-react moment react-big-calendar react-bootstrap react-datepicker react-router-dom

With all the packages installed, we can start writing the code. First, we replace the existing code in App.js with:

import React from "react";
import { Router, Route } from "react-router-dom";
import HomePage from "./HomePage";
import { createBrowserHistory as createHistory } from "history";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";
import "./App.css";
import "react-big-calendar/lib/css/react-big-calendar.css";
import "react-datepicker/dist/react-datepicker.css";
const history = createHistory();

function App({ calendarStore }) {
  return (
    <div>
      <Router history={history}>
        <Navbar bg="primary" expand="lg" variant="dark">
          <Navbar.Brand href="#home">Calendar App</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Navbar.Collapse id="basic-navbar-nav">
            <Nav className="mr-auto">
              <Nav.Link href="/">Home</Nav.Link>
            </Nav>
          </Navbar.Collapse>
        </Navbar>
        <Route
          path="/"
          exact
          component={props => (
            <HomePage {...props} calendarStore={calendarStore} />
          )}
        />
      </Router>
    </div>
  );
}

export default App;

We add the React Bootstrap top bar in here with a link to the home page. Also, we add the route for the home page in here with the MobX calendarStore passed in.

Also, we import the styles for the date picker and calendar here so that we can use them throughout the app.

Next in App.css , replace the existing code with:

.page {
  padding: 20px;
}

.form-control.react-datepicker-ignore-onclickoutside,
.react-datepicker-wrapper {
  width: 465px !important;
}

.react-datepicker__current-month,
.react-datepicker-time__header,
.react-datepicker-year-header,
.react-datepicker__day-name,
.react-datepicker__day,
[class^="react-datepicker__day--*"],
.react-datepicker__time-list-item {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
    "Droid Sans", "Helvetica Neue", sans-serif;
}

to add some padding to our page, change the width of the date picker input and change the font of the date picker.

Next, create a file called CalendarForm.js in the src folder and add:

import React from "react";
import Form from "react-bootstrap/Form";
import Col from "react-bootstrap/Col";
import DatePicker from "react-datepicker";
import Button from "react-bootstrap/Button";
import {
  addCalendar,
  editCalendar,
  getCalendar,
  deleteCalendar
} from "./requests";
import { observer } from "mobx-react";

const buttonStyle = { marginRight: 10 };

function CalendarForm({ calendarStore, calendarEvent, onCancel, edit }) {
  const [start, setStart] = React.useState(null);
  const [end, setEnd] = React.useState(null);
  const [title, setTitle] = React.useState("");
  const [id, setId] = React.useState(null);

  React.useEffect(() => {
    setTitle(calendarEvent.title);
    setStart(calendarEvent.start);
    setEnd(calendarEvent.end);
    setId(calendarEvent.id);
  }, [
    calendarEvent.title,
    calendarEvent.start,
    calendarEvent.end,
    calendarEvent.id
  ]);

  const handleSubmit = async ev => {
    ev.preventDefault();
    if (!title || !start || !end) {
      return;
    }

    if (+start > +end) {
      alert("Start date must be earlier than end date");
      return;
    }
    const data = { id, title, start, end };
    if (!edit) {
      await addCalendar(data);
    } else {
      await editCalendar(data);
    }
    const response = await getCalendar();
    const evs = response.data.map(d => {
      return {
        ...d,
        start: new Date(d.start),
        end: new Date(d.end)
      };
    });
    calendarStore.setCalendarEvents(evs);
    onCancel();
  };
  const handleStartChange = date => setStart(date);
  const handleEndChange = date => setEnd(date);
  const handleTitleChange = ev => setTitle(ev.target.value);

  const deleteCalendarEvent = async () => {
    await deleteCalendar(calendarEvent.id);
    const response = await getCalendar();
    const evs = response.data.map(d => {
      return {
        ...d,
        start: new Date(d.start),
        end: new Date(d.end)
      };
    });
    calendarStore.setCalendarEvents(evs);
    onCancel();
  };

  return (
    <Form noValidate onSubmit={handleSubmit}>
      <Form.Row>
        <Form.Group as={Col} md="12" controlId="title">
          <Form.Label>Title</Form.Label>
          <Form.Control
            type="text"
            name="title"
            placeholder="Title"
            value={title || ""}
            onChange={handleTitleChange}
            isInvalid={!title}
          />
          <Form.Control.Feedback type="invalid">{!title}</Form.Control.Feedback>
        </Form.Group>
      </Form.Row>

      <Form.Row>
        <Form.Group as={Col} md="12" controlId="start">
          <Form.Label>Start</Form.Label>
          <br />
          <DatePicker
            showTimeSelect
            className="form-control"
            selected={start}
            onChange={handleStartChange}
          />
        </Form.Group>
      </Form.Row>

      <Form.Row>
        <Form.Group as={Col} md="12" controlId="end">
          <Form.Label>End</Form.Label>
          <br />
          <DatePicker
            showTimeSelect
            className="form-control"
            selected={end}
            onChange={handleEndChange}
          />
        </Form.Group>
      </Form.Row>
      <Button type="submit" style={buttonStyle}>
        Save
      </Button>
      <Button type="button" style={buttonStyle} onClick={deleteCalendarEvent}>
        Delete
      </Button>
      <Button type="button" onClick={onCancel}>
        Cancel
      </Button>
    </Form>
  );
}

export default observer(CalendarForm);

This is the form for adding and editing the calendar entries. We add the React Bootstrap form here by adding the Form component. The Form.Control is also from the same library. We use it for the title text input.

The other 2 fields are the start and end dates. We use React Datepicker in here to let users select the start and end dates of a calendar entry. In addition, we enable the time picker to let users pick the time.

There are change handlers in each field to update the values in the state so users can see what they entered and let them submit the data later. The change handlers are handleStartChange , handleEndChange and handleTitleChange . We set the states with the setter functions generated by the useState hooks.

We use the useEffect callback to set the fields in the calendarEvent prop to the states. We pass all the fields we want to set to the array in the second argument of the useEffect function so that the states will be updated whenever the latest value of the calendarEvent prop is passed in.

In the handleSubmit function, which is called when the form Save button is clicked. we have to call ev.preventDefault so that we can use Ajax to submit our form data.

If data validation passes, then we submit the data and get the latest and store them in our calendarStore MobX store.

We wrap observer outside the CalendarForm component so that we always get the latest values from calendarStore .

Next, we create our home page. Create a HomePage.js file in the src folder and add:

import React from "react";
import { Calendar, momentLocalizer } from "react-big-calendar";
import moment from "moment";
import Modal from "react-bootstrap/Modal";
import CalendarForm from "./CalendarForm";
import { observer } from "mobx-react";
import { getCalendar } from "./requests";

const localizer = momentLocalizer(moment);

function HomePage({ calendarStore }) {
  const [showAddModal, setShowAddModal] = React.useState(false);
  const [showEditModal, setShowEditModal] = React.useState(false);
  const [calendarEvent, setCalendarEvent] = React.useState({});
  const [initialized, setInitialized] = React.useState(false);

  const hideModals = () => {
    setShowAddModal(false);
    setShowEditModal(false);
  };

  const getCalendarEvents = async () => {
    const response = await getCalendar();
    const evs = response.data.map(d => {
      return {
        ...d,
        start: new Date(d.start),
        end: new Date(d.end)
      };
    });
    calendarStore.setCalendarEvents(evs);
    setInitialized(true);
  };

  const handleSelect = (event, e) => {
    const { start, end } = event;
    const data = { title: "", start, end, allDay: false };
    setShowAddModal(true);
    setShowEditModal(false);
    setCalendarEvent(data);
  };

  const handleSelectEvent = (event, e) => {
    setShowAddModal(false);
    setShowEditModal(true);
    let { id, title, start, end, allDay } = event;
    start = new Date(start);
    end = new Date(end);
    const data = { id, title, start, end, allDay };
    setCalendarEvent(data);
  };

  React.useEffect(() => {
    if (!initialized) {
      getCalendarEvents();
    }
  });

  return (
    <div className="page">
      <Modal show={showAddModal} onHide={hideModals}>
        <Modal.Header closeButton>
          <Modal.Title>Add Calendar Event</Modal.Title>
        </Modal.Header>

        <Modal.Body>
          <CalendarForm
            calendarStore={calendarStore}
            calendarEvent={calendarEvent}
            onCancel={hideModals.bind(this)}
            edit={false}
          />
        </Modal.Body>
      </Modal>

      <Modal show={showEditModal} onHide={hideModals}>
        <Modal.Header closeButton>
          <Modal.Title>Edit Calendar Event</Modal.Title>
        </Modal.Header>

        <Modal.Body>
          <CalendarForm
            calendarStore={calendarStore}
            calendarEvent={calendarEvent}
            onCancel={hideModals.bind(this)}
            edit={true}
          />
        </Modal.Body>
      </Modal>
      <Calendar
        localizer={localizer}
        events={calendarStore.calendarEvents}
        startAccessor="start"
        endAccessor="end"
        selectable={true}
        style={{ height: "70vh" }}
        onSelectSlot={handleSelect}
        onSelectEvent={handleSelectEvent}
      />
    </div>
  );
}

export default observer(HomePage);

We get the calendar entries and populate them in the calendar here. The entries are retrieved from the back end and then saved into the store. In the useEffect callback, we set to get the items when the page loads. We only do it when initialized is false so we won’t be reloading the data every time the page renders.

To open the modal for adding calendar entries, we set the onSelectSlot prop with our handler so that we can call setShowAddModal and setCalendarEvent to set open the modal and set the dates before opening the add calendar event modal.

Similarly, we set the onSelectEvent modal with the handleSelectEvent handler function so that we open the edit modal and set the calendar event data of the existing entry.

Each Modal have the CalendarForm component inside. We pass in the function for closing the modals into the form so that we can close them from the form. Also, we pass in the calendarStore and calendarEvent so that they can be manipulated in the CalendarForm.

We wrap observer outside the CalendarForm component so that we always get the latest values from calendarStore .

Next in index.js , we replace the existing code with:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { CalendarStore } from "./store";
const calendarStore = new CalendarStore();

ReactDOM.render(
  <App calendarStore={calendarStore} />,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: [https://bit.ly/CRA-PWA](https://bit.ly/CRA-PWA)
serviceWorker.unregister();

so that we can pass in the MobX calendarStore into the root App component.

Next, create a requests.js file in the src folder and add:

const APIURL = "[http://localhost:3000](http://localhost:3000)";
const axios = require("axios");

export const getCalendar = () => axios.get(`${APIURL}/calendar`);

export const addCalendar = data => axios.post(`${APIURL}/calendar`, data);

export const editCalendar = data =>
  axios.put(`${APIURL}/calendar/${data.id}`, data);

export const deleteCalendar = id => axios.delete(`${APIURL}/calendar/${id}`);

These are the functions for making the HTTP calls to manipulate the calendar entries.

Next, createstore.js in the src folder and add:

import { observable, action, decorate } from "mobx";

class CalendarStore {
  calendarEvents = [];

setCalendarEvents(calendarEvents) {
    this.calendarEvents = calendarEvents;
  }
}

CalendarStore = decorate(CalendarStore, {
  calendarEvents: observable,
  setCalendarEvents: action
});

export { CalendarStore };

to save the items in the store for access by all of our components.

Next in index.html , replace the existing code with:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>Calendar App</title>
    <link
      rel="stylesheet"
      href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

to add the Bootstrap CSS and rename the title.

Now all the hard work is done. All we have to do is use the JSON Server NPM package located at https://github.com/typicode/json-server for our back end.

Install it by running:

npm i -g json-server

Then run it by running:

json-server --watch db.json

In db.json , replace the existing content with:

{
  "calendar": []
}

Next, we run our app by running npm start in our app’s project folder and when the program asks you to run in a different port, select yes.

Categories
JavaScript

New JavaScript Features Coming in ES2020 That You Can Use Now

Since the release of ES6 in 2015, JavaScript has been evolving fast with tons of new features coming out in each iteration. The new versions of the JavaScript language specification have been updated yearly, with new language feature proposal being finalized faster than ever. This means that new features are getting incorporated into modern browsers and other JavaScript run-time engines, like Node.js, at a pace that we haven’t seen before.

In 2019, there are many new features that are in the ‘Stage 3’ phase, which means that it’s very close to being finalized, and browsers/Node are getting support for these features now. If we want to use them for production, we can use something like Babel to transpile them to older versions of JavaScript so they can be used in older browsers like Internet Explorer if needed.

In this article, we look at private fields in classes, optional chaining, nullish coalescing operator, and BigInts.

Private Fields in Classes

One of the latest proposals is a way to add private variables in classes. We will use the # sign to indicate that a class variable is a private variable. This way, we don’t need to use closures to hide private variables that we don’t want to expose to the outside world. For example, we can write a simple class to increment and decrement numbers like the following code:

class Counter {
  #x = 0;

  increment() {
    this.#x++;
  }

  decrement() {
    this.#x--;
  }

  getNum(){
    return this.#x;
  }
}

const c = new Counter();
c.increment();
c.increment();
c.decrement();
console.log(c.getNum());

We should get the value 1 from the console.log in the last line of the code above. #x is a private variable that can’t be accessed outside the class. So if we write:

console.log(c.#x);

We’ll get Uncaught SyntaxError: Private field '#x'.

Private variables are a much-needed feature of JavaScript classes. This feature is now in the latest version of Chrome and Node.js v12.

Optional Chaining Operator

Currently, if we want to access a deeply nested property of an object, we have to check if the property in each nesting level is defined by using long boolean expressions. For example, we have to check each property is defined in each level until we can access the deeply nested property that we want, as in the following code:

const obj = {
  prop1: {
    prop2: {
      prop3: {
        prop4: {
          prop5: 5
        }
      }
    }
  }
}

obj.prop1 &&
  obj.prop1.prop2 &&
  obj.prop1.prop2 &&
  obj.prop1.prop2.prop3 &&
  obj.prop1.prop2.prop3.prop4 &&
  console.log(obj.prop1.prop2.prop3.prop4.prop5);

Fortunately, the code above has every property defined in every level that we want to access, so we will actually get the value 5. However, if we have a nested object inside an object that is undefined or null in any level, then our program will crash and the rest won’t run if we don’t do a check for it. This means that we have to check every level to make sure that it won’t crash when it runs into a undefined or null object.

With the optional chaining operator, we just need to use the ?. to access nested objects. And if it bumps into a property that’s null or undefined, then it just returns undefined. With optional chaining, we can instead write:

const obj = {
  prop1: {
    prop2: {
      prop3: {
        prop4: {
          prop5: 5
        }
      }
    }
  }
}

console.log(obj?.prop1?.prop2?.prop3?.prop4?.prop5);

When our program runs into an undefined or null property, instead of crashing, we’ll just get back undefined. Unfortunately, this hasn’t been natively implemented in any browser, so we have use the latest version Babel to use this feature.

Nullish Coalescing Operator

Another issue that comes from null or undefined values is that we have to make a default value to set a variable in case the variable that we want is null or undefined. For example, if we have:

const y = x || 500;

Then we have to make a default value in case x is undefined when it’s being set to y by using the || operator. The problem with the || operator is that all falsy values like 0, false, or an empty string will be overridden with the default value which we don’t always want to do.

To solve this, there was a proposal to create a “nullish” coalescing operator, which is denoted by ??. With it, we only set the default value if the value if the first item is either null or undefined. For example, with the nullish coalescing operator, the expression above will become:

const y = x ?? 500;

For example, if we have the following code:

const x = null;
const y = x ?? 500;
console.log(y); // 500

const n = 0
const m = n ?? 9000;
console.log(m) // 0

y will be assigned the value 500 since x has the value null. However, m will be assigned the value 0 since n is not null or undfined. If we had used || instead of ??, then m would have been assigned the value 9000 since 0 is falsy.

Unfortunately, this feature isn’t in any browser or Node.js yet so we have to use the latest version of Babel to use this feature.

BigInt

To represent integers larger than 2 to the 53rd power minus 1 in JavaScript, we can use the BigInt object. It can be manipulated via normal operations like arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation. It can be constructed from numbers and hexadecimal or binary strings. Also, it supports bitwise operations like AND, OR, NOT, and XOR. The only bitwise operation that doesn’t work is the zero-fill right shift operator (>>>) because BigInts are all signed.

Also the unary + operator isn’t supported for adding Numbers and BitInts. These operations only are done when all the operands are BigInts. In JavaScript, a BigInt is not the same as a normal number. It’s distinguished from a normal number by having an n at the end of the number.

We can define a BigInt with the BigInt factory function. It takes one argument that can be an integer number or a string representing a decimal integer, hexadecimal string, or binary string. BigInt cannot be used with the built-in Math object. Also, when converting between numbers and BigInt and vice versa, we have to be careful because the precision of BigInt might be lost when a BigInt is converted into a number.

To define a BigInt, we can write the following if we want to pass in a whole number:

const bigInt = BigInt(1);
console.log(bigInt);

Then it would log 1n when we run the console.log statement. If we want to pass in a string into the factory function instead, we can write:

const bigInt = BigInt('2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222');
console.log(bigInt);

We can also pass in a hexadecimal number string by using a string that starts with 0x into the factory function:

const bigHex = BigInt("0x1fffffffffffff111111111");
console.log(bigHex);

The code above would log 618970019642690073311383825n when we run console.log on bigHex.

Likewise, we can pass in a binary string with a string that starts with 0b to get a BigInt:

const bigBin = BigInt("0b111111111111111000000000011111111111111111111111");
console.log(bigBin);

The code above would get us 281466395164671n when we run console.log on it. Passing in strings would be handy if the BigInt that we want to create is outside of the range that can be accepted by the number type.

We can also define a BigInt with a BigInt literal. We can just attach an n character to the end of a whole number. For example, we can write:

const bigInt = 22222222222222222222222222222222n;
console.log(bigInt);

Then we get 22222222222222222222222222222222n as the value of bigInt when we log the value. We can do arithmetic operations like +, -, /, *, % with BigInts like we do with numbers. All operands have to be BigInts if we want to do these operations with BigInts. However, if we get fractional results, the fractional parts will be truncated. BigInt is a big integer and it’s not for storing decimals. For example, in the examples below:

const expected = 8n / 2n;
console.log(expected) // 4n

const rounded = 9n / 2n;
console.log(rounded) // 4n

We get 4n for expected and rounded. This is because the fractional part is removed from the BigInt.

Comparison operators can be applied to BigInts. The operands can be either BigInt or numbers. For example, we can compare 1n to 1:

1n === 1

The code above would evaluate to false because BigInt and number aren’t the same type. But when we replace triple equals with double equals, like in the code below:

1n == 1

The statement above evaluate to true because only the value is compared. Note that in both examples, we mixed BigInt operands with number operands. This is allowed for comparison operators. BigInts and numbers can be compared together with other operators as well, like in the following examples:

1n < 9
// true

9n > 1
// true

9 > 9n
// false

9n > 9
// false

9n >= 9
// true

Also, they can be mixed together in one array and sorted. For example, if we have the following code:

const mixedNums = [5n, 6, -120n, 12, 24, 0, 0n];
mixedNums.sort();
console.log(mixedNums)

This feature works with the latest version of Chrome and Node.js now, so we can start using it in our apps. We can use Babel to make it work with older browsers.

Conclusion

We can use the # sign to indicate that a class variable is a private variable. This way, we don’t have to use closures to hide private variables that we don’t want to expose to the outside world. To solve problems with null and undefined values in objects, we have the optional chaining operator to access properties without checking that each level might be null or undefined. With the nullish coalescing operator, we can set default values for variables only for cases when it’s null or undefined. With BigInt objects, we can represent big numbers outside of the safe range of regular numbers in JavaScript and do the standard operations with them, except that fractional parts will be omitted from results.

Categories
JavaScript Answers

New JavaScript Features Coming in ES2020 That You Can Use Now (Part 2)

Since 2015, the JavaScript is evolving fast with lots of new features coming out in each iteration. New versions of the JavaScript language specification have been updated yearly, with new language feature proposals being finalized faster than ever.

This means that new features are getting incorporated into modern browsers and other JavaScript run-time engines like Node.js at a pace that we haven’t seen before. In 2019, there are many new features that are in the ‘Stage 3’ phase, which means that it’s very close to being finalized and browsers are getting support for these features now.

If we want to use them for production code, we can use something like Babel to transpile them to older versions of JavaScript so they can be used in older browsers like Internet Explorer if needed.

In this article, we’ll look at static fields and methods in classes and using the await keyword at the top level in regular code and module exports.

Static Fields and Methods in Classes

Static fields in classes is a new feature that’s coming soon to browsers and Node.js. It lets us add static fields for classes that do not need an instance of the class to be created to be accessed. They can both be private or public. It’s a handy replacement for enums. For example, with the latest proposal, we can write a class with static variables like in the following example:

class Fruit {
  static orange = 'orange';
  static grape = 'grape';
  static banana = 'banana';
  static apple = 'apple';

  static #secretPear = 'pear';

}

console.log(Fruit.orange);
console.log(Fruit.grape);
console.log(Fruit.banana);
console.log(Fruit.apple);

The example above should output:

orange
grape
banana
apple

If we try to access secretPear from outside the class, like in the following code:

console.log(Fruit.#secretPear);

We’ll get ‘Uncaught SyntaxError: Private field ‘#secretPear’ must be declared in an enclosing class.’

The keyword static is already working with methods in the classes since ES6. For example, we can declare a static method within a class by using the static keyword like in the following code:

class ClassStatic {
  static staticMethod() {
    return 'Static method has been called.';
  }
}

console.log(ClassStatic.staticMethod());

We should get ‘Static method has been called’ from the console.log output. As we can see the staticMethod method was called without instantiating the class ClassStatic class just like we expected. Static methods can also be private, we just add a # sign before the name of the method to make it private. For example, we can write the following code:

class ClassStatic {
  static #privateStaticMethod(){
    return 'Static method has been called.';
  }

  static staticMethod() {
    return ClassStatic.#privateStaticMethod();
  }
}

console.log(ClassStatic.staticMethod());

In the code above, we called the privateStaticMethod from the staticMethod to get the value of the return value from privateStaticMethod in staticMethod. Then when we call staticMethod by running ClassStatic.staticMethod() then we get ‘Static method has been called.’ However, we can’t call privateStaticMethod directly from outside the class, so if we run something like:

class ClassStatic {
  static #privateStaticMethod(){
    return 'Static method has been called.';
  }

  static staticMethod() {
    return ClassStatic.#privateStaticMethod();
  }
}

console.log(ClassStatic.#privateStaticMethod());

Then we will get an error. Also, note that we aren’t using this to access the privateStaticMethod , we used the class name ClassStatic to do it. If we use this to call it, as in this.#privateStaticMethod() , we’ll get an error or undefined depending on the Babel version we’re using. The static keyword for variables is partially supported within browsers. Chromium browsers like Chrome and Opera support it. Also, Node.js supports this keyword. Other browsers do not support it yet, so we’ve to use Babel to convert it into older JavaScript code for it to run on other browsers.

Top Level await

Currently, the await keyword can only be used inside async functions, which are functions that return promises. If we use the await keyword outside of an async function, we’ll get an error like ‘Cannot use keyword ‘await’ outside an async function’ and our program won’t run. Now we can use the await keyword without it being inside an async function. For example, we can use it with the following code:

const promise1 = new Promise((resolve)=>{
  setTimeout(()=> resolve('promise1 resolved'), 1000);
})

const promise2 = new Promise((resolve)=>{
  setTimeout(()=> resolve('promise2 resolved'), 1000);
})

const val1 = await promise1;
console.log(val1);
const val2 =  await promise2;
console.log(val2);

Then when we run this code above in Chrome, we’ll get:

promise1 resolved
promise2 resolved

With top-level await, we can run asynchronous code however we like, and we don’t have to go back to using the long-winded then function chaining with callbacks passed into each then function call. This is very clean and we don’t have to wrap it inside an async IIFE (immediately invoked function expression) to run it if we don’t want to declare a named function.

Another good thing about top-level await is that we can export it directly. This means that when the module exports something that has the await keyword, it will wait for the promise being the await keyword to resolve before running anything. On the surface, it acts as if it’s imported synchronously as usual, but underneath, it actually waits for the promise in the export expression to resolve. For example, we can write:

const promise1 = new Promise((resolve)=>{
  setTimeout(()=> resolve('promise1 resolved'), 1000);
})
export const val1 = await promise1;

When we import module1.js in module2.js :

import { val1 } from './module1';
console.log(`val1);

Then we can see the value of val1 logged as if it’s running synchronously, waiting for promise1 to resolve.

Conclusion

Static fields in classes is a new feature that’s coming soon to browsers and Node.js. It lets us add static fields for classes that do not need an instance of the class to be created to be accessed. They can both be private or public. It’s a handy replacement for enums.

Static methods have existed since ES6 and so it can be used now and has widespread support. Using the await keyword at the top level in regular code and module exports is very handy once it’s a finalized feature. It lets us use await to write asynchronous code outside of async functions, which is much cleaner than using then with callbacks.

Also, it can let us export the resolved value of asynchronous code and then import it and it use it in other modules as if it was synchronous code.

Categories
Nodejs

Using Events in Node.js

The core feature of Node.js is asynchronous programming. This means that code in Node.js may not be executed sequentially. Therefore, data may not be determined in a fixed amount of time. This means that to get all the data we need, we have to pass data around the app when the data is obtained. This can be done by emitting, listening to, and handling events in a Node.js app. When an event with a given name is emitted, the event can listen to the listener, if the listener is specified to listen to the event with the name. Evenemitter functions are called synchronously. The event listener code is a callback function that takes a parameter for the data and handles it. Node.js has an EventEmitter class — it can be extended by a new class created to emit events that can be listened to by event listeners.

Define Event Emitters

Here’s a simple example of creating and using the EventEmitter class:

const EventEmitter = require('events');

class Emitter extends EventEmitter {}

const eventEmitter = new Emitter();
eventEmitter.on('event', () => {
  console.log('event emitted!');
});

eventEmitter.emit('event');

We should get ‘event emitted!’ in the console log. In the code above, we created the Emitter which extends the EventEmitter class, which has the emit function we called in the last line. The argument of the emit function is the name of the event, which we listen to in this block of code:

eventEmitter.on('event', () => {
  console.log('event emitted!');
});

The callback function, after the 'event' argument above, is the event handler function, that runs when the event is received.

In the code above, we emitted an event. However, it’s not very useful since we didn’t pass any data with the emitted event when we emit the event so it doesn’t do much. Therefore, we want to send data with the event so that we can pass data around so that we can do something useful in the event listener. To pass data when we emit an event, we can pass in extra arguments after the first argument, which is the event name. For instance, we can write the following code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('event', (a, b) => {
  console.log(a, b);
});

eventEmitter.emit('event', 'a', 'b');

If we run the code above, we get ‘a’ and ‘b’ in the console.log statement insider the event handler callback function. As we see, we can pass in multiple arguments with the emit function to pass data into event handlers that subscribe to the event. After the first one, the arguments in the emit function call are all passed into the event listener’s callback function as parameters, so they can be accessed within the event listener callback function.

We can also access the event emitter object inside the event listener callback function. All we have to do is change the arrow function of the callback to a tradition function, as in this code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('event', function(a, b){
  console.log(a, b);
  console.log(`Instance of EventEmitter: ${this instanceof EventEmitter}`);
  console.log(`Instance of Emitter: ${this instanceof Emitter}`);
});

eventEmitter.emit('event', 'a', 'b');

If we run the code above, we get this logged in the console.log statements inside the event listener callback function:

a b
Instance of EventEmitter: true
Instance of Emitter: true

On the other hand, if we have the following:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('event', (a, b) => {
  console.log(a, b);
  console.log(`Instance of EventEmitter: ${this instanceof EventEmitter}`);
  console.log(`Instance of Emitter: ${this instanceof Emitter}`);
});

eventEmitter.emit('event', 'a', 'b');

Then we get this logged in the console.log statements inside the event listener callback function.:

a b
Instance of EventEmitter: false
Instance of Emitter: false

This is because arrow functions do not change the this object inside it. However, tradition functions do change the content of the this object.

EventEmitter calls all listeners synchronously, in the order that they’re registered. This eliminates the chance of race conditions and other logic errors. To handle events asynchronously, we can use the setImmediate() or the process.nextTick() methods:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('event', (a, b) => {
  setImmediate(() => {
    console.log('event handled asychronously');
  });
});
eventEmitter.emit('event', 'a', 'b');

In the code above, we put the console.log inside a callback function of the setImmediate function, which will run the event handling code asynchronously instead of synchronously.

Events are handled every time they’re emitted. For example, if we have:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();
let x = 1;

eventEmitter.on('event', (a, b) => {
  console.log(x++);
});

for (let i = 0; i < 5; i++){
  eventEmitter.emit('event');
}

Since we emitted the ‘event’ event five times, we get this:

1
2
3
4
5

If we want to emit an event and handle it only the first time it’s emitted, then we use the eventEmitter.once() function, as in this code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();
let x = 1;

eventEmitter.once('event', (a, b) => {
  console.log(x++);
});

for (let i = 0; i < 5; i++){
  eventEmitter.emit('event');
}

As expected, we only get this logged in the console.log statement of the event handler above:

1

Error Handling

If an error event is emitted in the case of errors, it’s treated as a special case within Node.js. If the EventEmitter doesn’t have at least one error event listener register and an error is emitted, the error is thrown, and the stack trace of the error will be printed, and the process will exit. For example, if we have the following code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.emit('error', new Error('Error occured'));

Then we get something like this and the program exits:

Error [ERR_UNHANDLED_ERROR]: Unhandled error. (Error: Error occured
    at evalmachine.<anonymous>:5:28
    at Script.runInContext (vm.js:133:20)
    at Object.runInContext (vm.js:311:6)
    at evaluate (/run_dir/repl.js:133:14)
    at ReadStream.<anonymous> (/run_dir/repl.js:116:5)
    at ReadStream.emit (events.js:198:13)
    at addChunk (_stream_readable.js:288:12)
    at readableAddChunk (_stream_readable.js:269:11)
    at ReadStream.Readable.push (_stream_readable.js:224:10)
    at lazyFs.read (internal/fs/streams.js:181:12))
    at Emitter.emit (events.js:187:17)
    at evalmachine.<anonymous>:5:14
    at Script.runInContext (vm.js:133:20)
    at Object.runInContext (vm.js:311:6)
    at evaluate (/run_dir/repl.js:133:14)
    at ReadStream.<anonymous> (/run_dir/repl.js:116:5)
    at ReadStream.emit (events.js:198:13)
    at addChunk (_stream_readable.js:288:12)
    at readableAddChunk (_stream_readable.js:269:11)
    at ReadStream.Readable.push (_stream_readable.js:224:10)

To prevent the Node.js program from crashing, we can listen to the error event with a new event listener and handle the error gracefully in the error event handler. For example, we can write:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('error', (error) => {
  console.log('Error occurred');
});

eventEmitter.emit('error', new Error('Error occurred'));

Then we get “error occurred” logged. We can also get the error content with the error parameter of the event handler callback function. If we log it, as in this code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('error', (error) => {
  console.log(error);
});

eventEmitter.emit('error', new Error('Error occurred'));

We will get something like this:

Error: Error occurred
    at evalmachine.<anonymous>:7:28
    at Script.runInContext (vm.js:133:20)
    at Object.runInContext (vm.js:311:6)
    at evaluate (/run_dir/repl.js:133:14)
    at ReadStream.<anonymous> (/run_dir/repl.js:116:5)
    at ReadStream.emit (events.js:198:13)
    at addChunk (_stream_readable.js:288:12)
    at readableAddChunk (_stream_readable.js:269:11)
    at ReadStream.Readable.push (_stream_readable.js:224:10)
    at lazyFs.read (internal/fs/streams.js:181:12)

More Ways to Deal with Events

Node.js will emit one special event without writing any code to emit the event: The newListener . The newListener event is emitted before a listener is added to the internal array of listeners. For example, if we have the following code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();
eventEmitter.on('newListener', (`event, listener`) => {
console.log(`event`);
});

Then we get something like this logged:

Emitter {
  _events: [Object: null prototype] { newListener: [Function] },
  _eventsCount: 1,
  _maxListeners: undefined }

This happens even when no events are emitted. Whatever is in the handler will be run before the code in event handlers for any other events.

The removeListener function can be used to stop event listener functions from listening to events. This takes two arguments: The first is a string that represents the event name, the second is the function that you want to stop using to listen to events. For example, if we want to stop listening to the “event” event with our listener function, then we can write this:

const EventEmitter = require('events');
class Emitter extends EventEmitter { }
const eventEmitter = new Emitter();

const listener = () => {
  console.log('listening');
}

eventEmitter.on('event', listener)

setInterval(() => {
  eventEmitter.emit('event');
}, 300);

setTimeout(() => {
  console.log("removing");
  eventEmitter.removeListener('event', listener);
}, 2000);

Then we get something like this in the output:

Timeout {
  _called: false,
  _idleTimeout: 2000,
  _idlePrev: [TimersList],
  _idleNext: [TimersList],
  _idleStart: 1341,
  _onTimeout: [Function],
  _timerArgs: undefined,
  _repeat: null,
  _destroyed: false,
  [Symbol(unrefed)]: false,
  [Symbol(asyncId)]: 10,
  [Symbol(triggerId)]: 7 }listening
listening
listening
listening
listening
listening
removing

The event emitter emits the “event” event in the code above once every 300 milliseconds. This is listened to by the listener function, until it’s been prevented from listening again by calling the removeListener function with the “event” as the event name the listener event listener function in the callback of the setTimeout function.

Multiple event listeners can register for a single event. By default, the limit for the maximum number of event listeners is ten. We can change this with the defaultMxListeners function in the EventEmitter class. We can set it to any positive number. If it’s not a positive number, then a TypeError is thrown. If more listeners than the limit are registered then a warning will be output. For example, if we run the following code to register 11 event listeners for the “event” event:

const EventEmitter = require('events');
class Emitter extends EventEmitter { }
const eventEmitter = new Emitter();

const listener = () => {
  console.log('listening');
}

for (i = 1; i <= 11; i++){
  eventEmitter.on('event', listener);
}

eventEmitter.emit('event');

When we run the code above, we get this:

listening
listening
listening
listening
listening
listening
listening
listening
listening
listening
listening
(node:345) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit

However, if we call setMaxListeners to set it to getMaxListeners() + 1, which is 11 listeners, as seen in the following code:

const EventEmitter = require('events');
class Emitter extends EventEmitter { }
const eventEmitter = new Emitter();
eventEmitter.setMaxListeners(eventEmitter.getMaxListeners() + 1);

const listener = () => {
  console.log('listening');
}

for (i = 1; i <= 11; i++){
  eventEmitter.on('event', listener);
}

eventEmitter.emit('event');

Then we get the following logged:

listening
listening
listening
listening
listening
listening
listening
listening
listening
listening
listening

How to attach and remove event listeners

To get the names of the events that are listened to, we can use the eventNames function. The function takes no arguments and returns an array of event identifiers, which may include strings or symbols.

For example, we can use it as in the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
eventEmitter.on('event1', () => {});
eventEmitter.on('event2', () => {});

const sym = Symbol('event3');
eventEmitter.on(sym, () => {});

console.log(eventEmitter.eventNames());

When we run the code above, we get the following logged:

[ 'event1', 'event2', Symbol(event3) ]

To get the maximum number of listeners that can be attached to a single event, we can use the getMaxListeners function. It takes no arguments and returns the current maximum number of listeners that can be attached to one event.

The maximum number of event listeners that can be attached to the event can be set by the setMaxListeners function and the default value is 10. For example, we can use it as in the following example:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
console.log(eventEmitter.getMaxListeners());

We can use the listenerCount function to get the number of event listeners attached to an event. It takes one string or symbol argument for the event name and returns an integer with the number of event listeners attached to the event with the event name passed into the argument.

For example, we can use it as in the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

for (let i = 1; i <= 5; i++) {
  eventEmitter.on('event', () => { });
}

console.log(eventEmitter.listenerCount('event'));

If the code above is run, we get 5 logged as we attached five event listeners to the event event.

To get the event listeners functions attached to a given event, we can use the listeners function. It takes a string or symbol with the event name and returns an array of event listener functions.

It returns a copy of the listeners rather than the original ones. For instance, we can use it as in the following example:

[ '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)' ]

To attach an event listener to the beginning of the array listeners for a given event, we can use the prependListener function.

The function takes the event name as the first argument and the event listener function as the second argument. For example, we can write the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

for (let i = 1; i <= 5; i++) {
  eventEmitter.on('event', () => console.log(`Listener ${i} for 'event' event invoked`));
}

eventEmitter.prependListener('event', () => console.log('Prepended listener invoked'))

console.log(eventEmitter.listeners('event').map(f => f.toString()));

If we run the code above, we get the following logged:

[ '() => console.log('Prepended listener invoked')',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)' ]

As we can see, the prepended event listener is in the first slot of the array. If we add eventEmitter.emit('event') to emit the event event, as in the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

for (let i = 1; i <= 5; i++) {
  eventEmitter.on('event', () => console.log(`Listener ${i} for 'event' event invoked`));
}

eventEmitter.prependListener('event', () => console.log('Prepended listener invoked'))

console.log(eventEmitter.listeners('event').map(f => f.toString()));

Then, we get the following logged with the console.log statements:

[ '() => console.log('Prepended listener invoked')',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)' ]
Prepended listener invoked
Listener 1 for 'event' event invoked
Listener 2 for 'event' event invoked
Listener 3 for 'event' event invoked
Listener 4 for 'event' event invoked
Listener 5 for 'event' event invoked

We also see that the listener that we prepended to the event listener array is invoked first since the listeners are handled in the same order as they’re added into the array.

If we want to prepend an event listener that only handles the emitted event once, we can use the prependOnceListener instead. For example, we can use it as in the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
for (let i = 1; i <= 5; i++) {
  eventEmitter.on('event', () => console.log(`Listener ${i} for 'event' event invoked`));
}
eventEmitter.prependOnceListener('event', () => console.log('Prepended once listener invoked'))

eventEmitter.emit('event');
eventEmitter.emit('event');

If we run the code above, we can see the following logged in the console.log statements:

Prepended once listener invoked
Listener 1 for 'event' event invoked
Listener 2 for 'event' event invoked
Listener 3 for 'event' event invoked
Listener 4 for 'event' event invoked
Listener 5 for 'event' event invoked
Listener 1 for 'event' event invoked
Listener 2 for 'event' event invoked
Listener 3 for 'event' event invoked
Listener 4 for 'event' event invoked
Listener 5 for 'event' event invoked

As we can see, the listener that we prepended with the prependOnceListener only runs once. This is what we expect if we attach an event listener with the prependOnceListener function.

If we want to remove all event listeners for a given event, we can use the removeAllListeners function. It takes a string or symbol argument for the event identifier.

Note that it’s bad practice to remove event listeners that are attached elsewhere in the code when the EventEmitter is created in some other component of your program. It returns a reference to the EventEmitter so that the calls can be chained.

For instance, we can write the following code to remove all event listeners for the event event:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
for (let i = 1; i <= 5; i++) {
  eventEmitter.on('event', () => console.log(`Listener ${i} for 'event' event invoked`));
}
eventEmitter.prependOnceListener('event', () => console.log('Prepended once listener invoked'))

console.log('Before remove all listenersn', eventEmitter.listeners('event').map(f => f.toString()));

eventEmitter.removeAllListeners('event');

console.log('After remove all listenersn', eventEmitter.listeners('event'));

When the code above is run, we get the following logged:

Before remove all listeners
 [ '() => console.log('Prepended once listener invoked')',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)' ]
After remove all listeners
 []

To remove a single event listener for a given event, we can use the removeListener function. It takes an argument with the string or symbol for an event identifier, and a reference to the listener that’s attached to the given event.

It returns a reference to the EventEmitter so that the calls can be chained. We can use it as in the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

const listener1 = () => console.log('listener1 invoked');
const listener2 = () => console.log('listener2 invoked');

eventEmitter.on('event', listener1);
eventEmitter.on('event', listener2);

console.log('Before remove listenersn', eventEmitter.listeners('event').length);

eventEmitter
  .removeListener('event', listener1)
  .removeListener('event', listener2);

console.log('After remove listenersn', eventEmitter.listeners('event').length);

When we run the code above, we get the following logged with the console.log statements:

Before remove listeners
 2
After remove listeners
 0

As we can see, the removeListener can be chained and lets us remove the listeners one at a time. The listeners that were removed are no longer listening to the events.

If a single listener is attached to an event multiple times, then it might be called the number of times that the event listener is attached to remove all the event listeners.

If the removeListener function is called once in this scenario, it will remove the most recently added event listener. The event listeners array for the given event will be updated when the event listener is removed.

To get an array of event listeners for an event including the wrappers, we can use the rawListeners function. For example, we can use it as the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

const listener1 = () => console.log('listener1 invoked');
const listener2 = () => console.log('listener2 invoked');

eventEmitter.once('event', listener1);
eventEmitter.on('event', listener2);

const rawListeners = eventEmitter.rawListeners('event');
rawListeners[0].listener();

If we run the code above, we see that listener1 invoked is logged. This is because that the eventEmitter.once wraps the listener function that’s passed into it.

This is returned along with the actual event listener. Therefore, we can run the actual event listener function by running rawListeners[0].listener();.

This doesn’t work with the second listener because there’s no wrapper to wrap the listener function when we call eventEmitter.on to attach a listener.

An important feature of Node.js is asynchronous programming. This means that code in Node.js may not be executed sequentially. Therefore, data may not be determined in a fixed amount of time.

This means that to get all the data we need, we have to pass data around the app when the data is obtained. We can emit events and handle them within a Node.js app.

When an event with a given name is emitted, the event can listen to the listener if the listener is specified to listen to the event with the name. Event emitter functions are called synchronously.

The event listener code is a callback function that takes a parameter for the data and handles it. Node.js has an EventEmitter class that can be extended by a new class that we create to emit events that can be listened to by event listeners.

With the EventEmitter class, we can add and remove event listeners with built-in functions. We can remove all of them at the same time or one at a time.

However, we shouldn’t remove listeners from event emitters that aren’t written by us as it causes confusion to other people that are working on the code since other programmers do not expect built-in listeners from other modules to be removed.