Vue 3 Composition API Lifecycle Hooks

Lately I’ve been working with Vue 3, and I figured it would be helpful to share more insight into this JavaScript framework the more I delve into it. When it comes to Vue’s lifecycle hooks, I think it’s useful to understand when and where you can use them.

Vue 3 lifecycle hooks are special functions that allow you to run code at specific moments in the life of a Vue component. Each hook corresponds to a different phase in the component’s existence, giving you the ability to perform tasks or respond to events at specific times. Let’s break down what each hook does:

onBeforeMount

For tasks that should complete just before the component is mounted, use onBeforeMount. This hook is ideal for actions like pre-fetching data or performing any operations that need to be completed before the component becomes visible.

import { onBeforeMount } from 'vue';

onBeforeMount(() => {
  // Tasks to perform just before mounting
});

onMounted

To interact with the DOM or execute operations after the component has been successfully mounted, use the onMounted lifecycle hook. This is a great time to access and manipulate DOM elements.

import { onMounted } from 'vue';

onMounted(() => {
  // Access and manipulate the DOM
});

onUpdated

When responsiveness to changes in state or props is crucial after an update, you can utilize onUpdated. This hook allows you to react dynamically to modifications in the component’s state or props, enabling you to trigger side effects or additional logic during re-renders.

import { onUpdated } from 'vue';

onUpdated(() => {
  // React to changes in state or props
});

onBeforeUnmount

For cleanup tasks and resource release before a component is unmounted, use onBeforeUnmount. This hook ensures that your component gracefully removes any resources acquired during its lifecycle.

import { onBeforeUnmount } from 'vue';

onBeforeUnmount(() => {
  // Cleanup tasks before unmounting
});

onErrorCaptured

For handling errors occurring within the component’s lifecycle, turn to onErrorCaptured. This hook allows you to catch and manage errors internally or propagate them to a higher level for comprehensive error handling.

import { onErrorCaptured } from 'vue';

onErrorCaptured((error, instance, info) => {
  // Handle errors within the component
});

JavaScript Design Patterns

Design patterns are guidelines for solving common problems in software development. By learning design patterns, you can quickly and easily communicate designs to other software engineers. Here is an overview of some of the common JavaScript design patterns:

Singleton

The Singleton Pattern ensures that a class has only one instance, and provides a global access point to it. This instance can be shared throughout an application, which makes Singletons great for managing global state.

Example:

let instance;

class Example {
  constructor() {
    if (instance) {
      throw new Error("You've already created an instance!")
    }
    this.example = example;
    instance = this;
  }

  getExample() {
    return this.example;
  }
}

Pros to using the Singleton Pattern

✅ You can potentially save a lot of memory since you don’t have to set up memory for a new instance.

Cons to using the Singleton Pattern

We no longer need to explicitly create Singletons since ES2015 Modules are Singletons by default.

The global variables are accessible throughout the code. This can lead to problems like race conditions.

When importing a module, it might not be obvious that the module is importing a Singleton which can lead to unexpected value changes within the Singleton.

Proxy

The Proxy pattern uses a Proxy to serve as an interface to control interactions to target objects.

Example:

const targetObject = {
  name: "User",
  message: "hello world"
}

const handler = {
  get(targetObject, prop, receiver) {
    return "!";
  }
}

const proxy = new Proxy(targetObject, handler);

console.log(proxy.message); // !

Pros to using the Proxy Pattern

✅ It’s easier to add functionality with specific objects (i.e. logging, debugging, notifications, validation, etc).

Cons to using the Proxy Pattern

Could cause performance issues from executing handlers on every object.

Observer

The Observer Pattern is the most commonly used design pattern in the real world. It defines a one-to-many dependency between objects so that when one object changes its state, all of its dependents are notified and updated automatically.

Example:

class Observable {
  constructor() {
    this.observers = [];
  }

  subscribe(exampleFunction) {
    this.observers.push(exampleFunction);
  }

  unsubscribe(exampleFunction) {
    this.observers = this.observers.filter((observer) => observer !== exampleFunction);
  }
 
  notify(data) {
    this.observers.forEach((observer) => observer(data));
  }
}

Pros to using the Observer Pattern

✅ Observer objects (that handle the received data) can be decoupled/coupled easily with the observable object (monitors the events).

Cons to using the Observer Pattern

Could potentially have performance issues because of the time it takes to notify all subscribers (i.e. if there are too many subscribers, if the logic becomes too complicated).

Factory

The Factory Pattern wraps a constructor for different types of objects and returns instances of the objects.

Example:

const createItem = (name, message) => ({
  createdAt: Date.now(),
  name,
  message,
  quote: `${name} said "${message}"`,
});

createItem("User", "Hello World!");

Pros to using the Factory Pattern

✅ Keeps code DRY and is handy when we need to create several objects that share the same properties.

Cons to using the Factory Pattern

Might be more memory efficient to create new instances instead of new objects.

Prototype

The Prototype Pattern creates new objects and returns objects that are initialized with values copied from the prototype. It is a helpful way to share properties among many objects of the same type.

Example:

const createUser = (name, message) => ({
  name,
  message,
  speak() {
    console.log(`${name} said "${message}"`);
  },
  walking() {
    console.log(`${name} is walking!`);
  },
});

const user1 = createUser("Jill", "Hello world!");
const user2 = createUser("Jack", "Good morning!");

Pros to using the Prototype Pattern

✅ More efficient with memory since we can access properties that aren’t defined directly on the object. This allows us to avoid duplication of properties and methods.

Cons to using the Prototype Pattern

Issues with readability – if a class is extended quite a few times, it can be hard to know where specific properties come from.

Additional Resources

patterns.dev

JavaScript Closures

JavaScript closures allow functions to remember the scope in which they were created, even when they are executed outside that scope. This ability to capture and retain the lexical scope is what makes closures a powerful feature.

How Closures Work

To understand closures, it’s essential to grasp the concept of lexical scope. Lexical scope refers to the way in which variable names are resolved in nested functions. Closures come into play when a function is defined within another function, creating a chain of lexical scopes.

When an inner function is returned from its outer function, it carries with it a reference to the entire scope chain in which it was defined. This reference allows the inner function to access variables and parameters from its outer function, even after the outer function has finished executing. This behavior is the essence of closures.

Practical Examples

Let’s explore a few practical examples to illustrate how closures work in real-world scenarios. We’ll look at scenarios such as data encapsulation, private variables, and callback functions to showcase the versatility and usefulness of closures.

  1. Data Encapsulation: Closures provide a way to encapsulate data within a function, preventing external code from directly accessing or modifying it. This promotes data integrity and reduces the likelihood of unintended side effects.
function createCounter() {
  let count = 0;

  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // Output: 1
console.log(counter()); // Output: 2

In this example, the createCounter function returns an inner function that has access to the count variable. The returned function serves as a counter, and the count variable is protected from external manipulation.

  1. Private Variables: Closures enable the creation of private variables within a function, allowing you to hide implementation details and expose only the necessary interfaces.
function createPerson(name) {
  let privateAge = 0;

  return {
    getName: function() {
      return name;
    },
    getAge: function() {
      return privateAge;
    },
    setAge: function(newAge) {
      if (newAge >= 0) {
        privateAge = newAge;
      }
    },
  };
}

const person = createPerson("John");
console.log(person.getName()); // Output: John
console.log(person.getAge()); // Output: 0
person.setAge(25);
console.log(person.getAge()); // Output: 25

In this example, the createPerson function returns an object with methods to access and modify private data (privateAge). This encapsulation ensures that the internal state of the object remains controlled.

  1. Callback Functions: Closures are commonly used in the context of callback functions. When a function is passed as an argument to another function and is executed later, it forms a closure, retaining access to the variables in its lexical scope.
function delayMessage(message, delay) {
  setTimeout(function() {
    console.log(message);
  }, delay);
}

delayMessage("Hello, World!", 2000);

In this example, the anonymous function inside setTimeout forms a closure, allowing it to access the message variable from the outer delayMessage function even after delayMessage has finished executing.

JavaScript closures provide developers with tools for creating modular, maintainable, and efficient code. By understanding how closures work and applying them judiciously, you can elevate your JavaScript programming skills and build robust applications.

JavaScript Iterators

Iterators in JavaScript refer to objects that provide a sequential method of accessing elements within a collection. Collections include data structures like arrays, strings, maps, and sets. Iterators offer a standardized approach to traversing these collections, providing a controlled and flexible alternative to traditional loops.

Creating an Iterator

Iterators include a method, within an object, named Symbol.iterator, responsible for returning an iterator object:

const myIterable = {
  [Symbol.iterator]: function () {
    // Insert iterator logic here
  },
};

The iterator object, in turn, must include a method named next, returning an object with value and done properties. The value property signifies the current element in the iteration, while the done property is a boolean indicating whether more elements are available for iteration.

const myIterator = {
  next: function () {
    // return { value: ..., done: ... }
  },
};

Working with Iterators

Many data structures, including arrays, strings, maps, and sets, inherently implement iterators:

const myArray = [1, 2, 3, 4, 5];
const arrayIterator = myArray[Symbol.iterator]();

console.log(arrayIterator.next()); // { value: 1, done: false }
console.log(arrayIterator.next()); // { value: 2, done: false }
console.log(arrayIterator.next()); // { value: 3, done: false }
// Iteration continues until done is true

Enhancing Code Readability

One of the main benefits of iterators is the improvement of code readability. By abstracting away the intricacies of looping, iterators allow developers to concentrate on the logic within the loop, rather than managing indices or counting iterations. This results in more concise and expressive code.

const myArray = [1, 2, 3, 4, 5];

// Traditional for loop
for (let i = 0; i < myArray.length; i++) {
  console.log(myArray[i]);
}

// Using iterator
const arrayIterator = myArray[Symbol.iterator]();
let iterationResult = arrayIterator.next();

while (!iterationResult.done) {
  console.log(iterationResult.value);
  iterationResult = arrayIterator.next();
}

Use Cases for Iterators

By offering a standardized interface for iterating over elements, iterators simplify the integration of custom objects into existing code. Additionally, iterators play a fundamental role in the for...of loop introduced in ECMAScript 6. This loop streamlines the process of iterating over iterable objects, resulting in more readable and concise code.

Understanding the Event Loop in JavaScript

What is the Event Loop?

The event loop is a continuous process that enables JavaScript to execute code, handle events, and manage asynchronous tasks. Unlike synchronous languages, JavaScript is single-threaded, meaning it can only execute one operation at a time. The event loop ensures that asynchronous operations, such as fetching data or handling user input, can be managed without blocking the main thread.

Phases of the Event Loop

The event loop consists of multiple phases, each playing a specific role in handling different types of tasks. Understanding these phases is crucial for grasping how JavaScript manages its execution flow.

  1. Call Stack:
    • The call stack is where synchronous code is executed.
    • Functions are pushed onto the stack and popped off when they complete.
  2. Callback Queue:
    • Asynchronous operations, such as API calls or user interactions, are processed in the callback queue.
    • Callbacks from these operations are queued up to be executed once the call stack is empty.
  3. Event Loop:
    • The event loop constantly checks if the call stack is empty.
    • If the stack is empty, it moves callbacks from the queue to the stack for execution.
  4. Microtask Queue:
    • Microtasks are high-priority tasks that are executed before the next event loop cycle.
    • Promises and certain APIs schedule tasks in the microtask queue.

How the Event Loop Handles Asynchronous Operations

Let’s take a look at how the event loop manages asynchronous tasks:

  1. setTimeout:
    • setTimeout allows you to schedule a function to run after a specified delay.
    • The specified function is added to the callback queue after the delay.
  2. Promises:
    • Promises are a powerful tool for handling asynchronous operations.
    • They use the microtask queue, ensuring their callbacks are executed before the next event loop cycle.
  3. Async/Await:
    • Async/Await, built on top of Promises, provides a more readable way to work with asynchronous code.
    • Under the hood, it still relies on the event loop and the microtask queue.

Understanding JavaScript Promises

JavaScript, as a versatile and dynamic programming language, has evolved over the years to offer developers powerful tools for handling asynchronous operations. Promises, introduced in ECMAScript 6 (ES6), have become a fundamental part of modern JavaScript development. Let’s take a look at what JavaScript promises are, as well as focusing on the micro-task queue and callback queue, which are crucial components in the asynchronous execution model.

JavaScript Promises

Promises are objects representing the eventual completion or failure of an asynchronous operation. They provide a cleaner and more structured way to handle asynchronous code compared to traditional callback functions. A promise can be in one of three states: pending, fulfilled, or rejected.

  1. Promise States:
    • Pending: The initial state when a promise is created.
    • Fulfilled: The state when the asynchronous operation is successfully completed.
    • Rejected: The state when the asynchronous operation encounters an error or fails.
  2. Creating Promises
    • Promises can be created using the Promise constructor. This constructor takes a function (executor) as its argument, which in turn takes two parameters: resolve and reject. Developers use these functions to signal the completion or failure of the asynchronous operation.
const myPromise = new Promise((resolve, reject) => {
  // Asynchronous operation
  if (operationSuccessful) {
    resolve(result);
  } else {
    reject(error);
  }
});

Micro-Task Queue

The micro-task queue is a key component in JavaScript’s event loop mechanism, responsible for handling micro-tasks. Micro-tasks are tasks that have higher priority than regular tasks and are executed after the current script has run to completion. Promises use the micro-task queue to schedule their callbacks.

  1. Micro-Tasks:
    • Micro-tasks include promise callbacks, process.nextTick, and Object.observe.
    • Micro-tasks are executed in a single cycle of the event loop, ensuring they are processed before the next rendering.
  2. Promise Execution Order:
    • When a promise is fulfilled or rejected, its callback is added to the micro-task queue.
    • Micro-tasks are executed in order, allowing promises to be resolved before other tasks in the callback queue.

Callback Queue

The callback queue, also known as the task queue or message queue, is another integral part of the event loop. It stores tasks that are ready to be executed, allowing asynchronous operations to be handled in a non-blocking manner.

  1. Callback Execution:
    • Tasks in the callback queue are executed after the micro-tasks.
    • Regular tasks, such as event handlers and I/O operations, are added to the callback queue.
  2. Order of Execution:
    • The event loop continuously checks the micro-task queue and executes micro-tasks before moving to the callback queue.
    • This order ensures that promises are resolved before other asynchronous operations in the callback queue.

JavaScript promises have transformed the way developers handle asynchronous code, offering a cleaner and more maintainable approach compared to traditional callbacks. Understanding the micro-task queue and callback queue is crucial for mastering the intricacies of the event loop and ensuring the efficient execution of asynchronous operations in JavaScript. As you delve deeper into asynchronous programming, a solid grasp of promises and the event loop will empower you to build robust and responsive applications.

What is the Difference Between null, NaN, and undefined in JavaScript?

The other day I was thinking about how earlier last year, I was interviewing like crazy, and sometimes I had recruiter phone screens where I was quizzed on various JavaScript terms. As I think of some of the questions I’ve been asked previously, I plan to write articles focusing on these questions and answers.

NaN

NaN means Not a number, and it denotes the value of an object is not a number. If you look at typeof NaN it will return 'number' since NaN is a numeric data type that is undefined as a real number. NaN can be returned if you do one of the following:

  1. Convert undefined into a number
  2. Convert a non-numeric string into a number
  3. Divide zero by zero
  4. Divide infinity by infinity
  5. Operation where the result is not a real number
  6. Method/expression’s operand is or gets coerced to NaN

null

null means the object is empty. It’s an assignment value that can be assigned to a variable that has no value.

undefined

undefined means that the object doesn’t have any value and is undefined. This happens when you declare a variable and don’t assign a value to it.

How I Made a Twitterbot with Node.js

The other day, I decided to make a Twitterbot to help streamline my marketing efforts for my art brand. I wanted to make a bot that would retweet tweets from a different user, selected from a list of users. I wanted the bot to also run once a day.

For my project, I used the following:

  • Node.js
  • twit – Twitter API client for Node.js
  • Heroku – platform for deployed applications

Creating an Application with Twitter

To get started, I used the Twitter application form to register my application. The process is pretty straight forward, and once it was completed, I was able to grab the necessary consumer_key, consumer_secret, access_token, and access_token_secret.

Building the Bot

To get started with Node.js, I created a new project directory. Then I ran npm init.

I ran the following commands to ensure I had the right packages to build the application:

npm install --save dotenv http twit

Next, I created the environment variables. I created a .env file in the directory, where I added in my project keys:

CONSUMER_KEY="add_it_here"
CONSUMER_SECRET="add_it_here"
ACCESS_TOKEN="add_it_here"
ACCESS_TOKEN_SECRET="add_it_here"

In my index.js file, I connected the .env variables to the application:

require("dotenv").config();
const config = {
  consumer_key: process.env.CONSUMER_KEY,
  consumer_secret: process.env.CONSUMER_SECRET,
  access_token: process.env.ACCESS_TOKEN,
  access_token_secret: process.env.ACCESS_TOKEN_SECRET
};

Then I added logic so the bot would retweet tweets from a list of users or retweet tweets tied to specific hashtags. First I set variables to use twit and to control the number of retweets the bot would make when the code was executed:

const twit = require("twit");
const Twitter = new twit(config);
const MAX_RT_COUNT = 1;

I found the Twitter ids for several users that I wanted to the bot to retweet. I used their ids instead of their screen names, in case a user decided to change their screen name. In order to keep track of who was who, I stored details about each user in a comment:

const USERS = [
  "15057943", // moma
  "14803372", // saam
  "5225991", // tate
  "22009731", // design museum
  "81783051", // artsy
  "17896874", // itsnicethat
  "158865339", // fastcodesign
  "21661279", // creative review
  "16336998", // print magazine
  "17623957", // design observer
  "418597196", // creativebloq
  "15446126", // design milk
  "18201801", // interior design
  "19038849" // how design
];

I made a function that would filter through the list of users, so that each day of the week, a different user would get a retweet from the bot:

const getUserOfTheDay = () => {
  let date = new Date();
  let dayOfMonth = date.getDate();
  let pickUserIndex = dayOfMonth % USERS.length;
  return USERS[pickUserIndex];
};

Next, I created the logic for retweeting tweets tied to different hashtags. I ultimately decided not to deploy this functionality, but I wanted to keep it around in case it’s useful in the future:

let retweetTags = async function() {
  try {
    const { data } = await Twitter.get("search/tweets", {
      q: "#art, #painting",
      result_type: "mixed",
      lang: "en"
    });

    const statuses = data.statuses.slice(0, MAX_RT_COUNT);
    // loop through the first n returned tweets
    for (const status of statuses) {
      // the post action
      const response = await Twitter.post("statuses/retweet/:id", {
        id: status.id_str
      });
      if (response) {
        console.log("Successfully retweeted");
      }
    }
  } catch (err) {
    // catch all log if the search/retweet could not be executed
    console.error("Err:", err);
  }
};

// retweetTags();

Lastly, I added the code for retweeting tweets from my list of users:

let retweetUsers = async function() {
  try {
    const { data } = await Twitter.get("users/show", {
      user_id: getUserOfTheDay()
    });

    const status = data.status;
    // make sure tweet isn't in reply to another user
    if (status.in_reply_to_status_id == null) {
      const response = await Twitter.post("statuses/retweet/:id", {
        id: status.id_str
      });
      if (response) {
        console.log("Successfully retweeted");
      }
    }
  } catch (err) {
    // catch all log if the search/retweet could not be executed
    console.error("Err:", err);
  }
};

To run the bot locally, I added retweetUsers(); to the file, then ran node index.js in the terminal.

Deploying the App to Heroku

Once I had my app up and running locally, I wanted to deploy it somewhere so it could run automatically. I did this by deploying it to Heroku.

I added a Procfile to the project, and added the line worker: node index.js to the file. Then I created a new Heroku project by running heroku create twitterbot-retweet. Next, I had to define the environment variables in Heroku by running heroku set:config key_name="key_value" in the terminal.

By accessing the Heroku dashboard online, I had to toggle on the worker for the bot (it was located under the “Dyno formation” section).

After deploying the bot, I realized I wanted the bot to run once every day, so I looked into options for how I could wake the application up once a day in order to execute the code. I ended up going with the simple setInterval method.

If your curious about how all the code looks, or want to see an up-to-date version of the project, you can find it on GitHub.

How to Add a Script to Minify your JavaScript in a package.json file

Recently I was working on a project where I wanted to set up a process that could combine and minify the JavaScript I had in a specific folder for a project. So how did I do it?

I decided to run the npm init command on my project folder. Within the package.json file, I had the following:

{
  "main": "scripts.js",
  "scripts": {
  "minify": "uglifyjs ./js/* -o scripts.min.js",
  "uglify": "npm run minify"
},
  "dependencies": {
  "uglify-js": "^2.8.8"
},
  "devDependencies": {
  "uglify-js": "^2.8.8"
 }
}

Once I ran npm install uglify-js --save, I made sure to specify the folder I wanted to run the process in (./js/*), and the file I wanted to create with the minified code (scripts.min.js). Next, all I had to do was run npm run uglify and my new scripts.min.js minified file was created.

Now whenever I make changes or add new files to my project’s JavaScript folder, I can run npm run uglify to bundle up and minify the code.

A Brief Overview of ES6

ECMAScript 6, or ES6, is a newer version of JavaScript. It has several unique capabilities that weren’t possible in previous versions of JavaScript, for example:

  • Variables can be set with the terms let and const
  • The let keyword is used for block scoping so it doesn’t affect global values
  • The const keyword is used to protect the value of certain variables – you use this keyword for values that shouldn’t be reassigned
  • It allows you to use template strings to tap into the functionality of template languages to format your JavaScript code with variables like so: `${yourVariableHere}`
  • The spread operator can turn elements of an array into arguments of a function call or into elements of an array literal
  • Allows us to make classes in JavaScript (can create one super class that other classes inherit)
  • Can pass default values in functions for when you don’t explicitly pass anything for them

In order for browsers to understand ES6, you need to transpile the code. Babel, Traceur, and Closure are used for transpiling code, although Babel is known for supporting the most ES6 features.

For larger scale projects, you can use build tools like Webpack with ES6 – this allows us to load our dependencies. Npm creates a package.json file that runs dependencies.