DEV Community

Cover image for Rebuilding a Front End Mentor Project in TypeScript
Yahaya Oyinkansola
Yahaya Oyinkansola

Posted on • Updated on

Rebuilding a Front End Mentor Project in TypeScript

Introduction

I am currently upskilling myself in TypeScript as part of the skills I need to land a Job as a Front End Developer. I started using Hitesh Choudhary's course on Youtube to understand how TypeScript works, Hitesh is a great teacher!, you should check out his Youtube channel to see what he does. In this article, I want to show you how I used TypeScript to rewrite the code for a Front End Mentor project I built with JavaScript, Front End Mentor is a great platform for building projects as a software developer, and not get stuck in tutorial hell. For those that are not familiar with TypeScript, let me give you a basic introduction to what it is

What is TypeScript?

TypeScript is a superset of JavaScript, meaning any valid JavaScript code is also valid TypeScript code, which makes it easy to adopt TypeScript incrementally in any existing JavaScript project. One of the most important features TypeScript brought into JavaScript is Type Checking. JavaScript is not strict with type checking, you might declare a variable to be a number, and later on change it to be a string somewhere else without any errors whatsoever (until you run the code of course). Here is an example

let num = 10;

num = "Hello";

console.log(num); // This is perfectly fine with JavaScript
Enter fullscreen mode Exit fullscreen mode

If you have used languages like Java, C#, C++ etc, you know that these languages have Type Checking built into them. TypeScript introduced Type checking into JavaScript to reduce errors in our code (like the one above), and help us write code that is production ready. With TypeScript, you declare the types for your variables so TypeScript can warn you when you are attempting to do something wrong. There are other features TypeScript has, but type checking is the most important feature TypeScript provides, which makes it easier to write code in JavaScript. To get started using TypeScript, you will need to install it globally on your system with npm

npm install -g typescript
Enter fullscreen mode Exit fullscreen mode

Then create a .ts file in your project folder e.g index.ts, and put this simple snippet

let num: number = 10
Enter fullscreen mode Exit fullscreen mode

To run this code with the TypeScript compiler, open your terminal and run this command

tsc index.ts
Enter fullscreen mode Exit fullscreen mode

This will convert the code from TypeScript to JavaScript!. Now that you have some basic understanding of what TypeScript is, and how to use it, let me show you how I used it to rewrite my FEM project

The Project

After learning some of the basics from Hitesh's course, I wanted to start putting them into practice straight away, so I was looking for some projects I did with JavaScript and found this Newsletter sign up project I did from Front End Mentor. Converting the code of this project wasn't easy, but it pushed me out of my comfort zone, and helped me learn a whole lot more about TypeScript which tutorials wouldn't have thought me (The best way of learning anything as a developer is by building projects). Here is the Github repo and Preview of the application if you like to check them out. Here is the HTML code of the application

    <main>
      <section id="newsletter" class="newsletter">
        <picture class="newsletter__image">
          <source
            srcset="assets/images/illustration-sign-up-desktop.svg"
            media="(min-width: 81.25em)"
          />

          <img src="assets/images/illustration-sign-up-mobile.svg" alt="" />
        </picture>

        <div class="newsletter__content">
          <h2>Stay updated!</h2>

          <p>Join 60,000+ product managers receiving monthly updates on:</p>

          <ul class="newsletter__features">
            <li class="feature__item">
              Product discovery and building what matters
            </li>

            <li class="feature__item">
              Measuring to ensure updates are a success
            </li>

            <li class="feature__item">And much more!</li>
          </ul>

          <form method="POST" class="newsletter__form">
            <label for="email">Email address</label>
            <input
              type="email"
              name="email"
              id="email"
              class="newsletter__input"
              placeholder="email@company.com"
            />

            <span class="form-error"></span>

            <button type="submit" class="subscribe__btn">
              Subscribe to monthly newsletter
            </button>
          </form>
        </div>
      </section>

      <section id="subscription-message" class="subscription__success d-none">
        <div class="subscription__message">
          <img
            src="./assets/images/icon-success.svg"
            class="subscription__icon"
            alt=""
          />

          <h3 class="subscription__heading">Thanks for subscribing!</h3>

          <p class="subscription__content">
            A confirmation email has been sent to
            <span class="subscription__email"></span>. Please open it and click
            the button inside to confirm your subscription.
          </p>
        </div>

        <button id="dismiss" class="subscribe__btn">Dismiss message</button>
      </section>
    </main>
Enter fullscreen mode Exit fullscreen mode

This is the JavaScript code

const newsletter = document.querySelector("#newsletter");
const newsletterForm = document.querySelector(".newsletter__form");
const subscriptionMessage = document.querySelector("#subscription-message");
const formInput = document.querySelector(".newsletter__input");
const formError = document.querySelector(".form-error");
const dismissBtn = document.querySelector("#dismiss");
const emailText = document.querySelector(".subscription__email");
const emailRegex = /^[a-zA-Z]+@[a-zA-Z]+\.[a-zA-Z]{2,4}$/;

newsletterForm.addEventListener("submit", (e) => {
  e.preventDefault();

  let status = true;

  if (formInput.value == "") {
    status = false;
    formInput.classList.add("newsletter__input--error");
    formError.innerHTML = "Email is required";
  }

  if (formInput.value != "" && formInput.value.match(emailRegex) === null) {
    status = false;

    formInput.classList.add("newsletter__input--error");
    formError.innerHTML = "Invalid email address";
  }

  if (status) {
    newsletter.classList.add("d-none");
    subscriptionMessage.classList.remove("d-none");
    emailText.innerHTML = formInput.value;
  }
});

formInput.addEventListener("input", () => {
  if (formInput.classList.contains("newsletter__input--error")) {
    formInput.classList.remove("newsletter__input--error");
    formError.innerHTML = "";
  }
});

dismissBtn.addEventListener("click", () => {
  newsletter.classList.remove("d-none");
  subscriptionMessage.classList.add("d-none");
});
Enter fullscreen mode Exit fullscreen mode

I won't explain the entire codebase, only the ones that had to do with me implementing TypeScript. The first thing I needed to do was define the types for some variables I declared above because when you select elements on the DOM, they have a default type of Element, this is a generic type that does not have specific properties and methods for HTML elements like <form>, <button> etc. The newsletterForm variable for example is a form element, if the type is not changed, we won't be able to access certain properties and methods available to form elements like e.preventDefault() because TypeScript is not smart enough to know we are selecting a form element. One way to specify the types we want in TypeScript is by using a technique called Type Assertion.

What is Type Assertion?

Type Assertion tells TypeScript you are certain about what the type of an element or variable is when TypeScript is not sure. For example, TypeScript is not sure what newsletterForm is, so we can use Type Assertion to let the compiler know that this variable is an HTML Form Element

const newsletterForm = document.querySelector(".newsletter__form") as HTMLFormElement;
Enter fullscreen mode Exit fullscreen mode

The next thing I am doing further down the code is attaching an event listener to the form element

newsletterForm.addEventListener("submit", (e) => {});
Enter fullscreen mode Exit fullscreen mode

I have told TypeScript that newsletterForm is a form element, but what if the element I assigned to the newsletterForm variable doesn't exist?, or the variable is not defined?, I will run into this problem

Uncaught TypeError: Cannot read properties of null

You must have seen this error in your console before, so how do you solve this issue?, by using what is called Optional Chaining. The optional chaining operator is used to safely access properties or methods of an object without running into unnecessary errors. If the object is not defined, the code is short circuited, and any code written after it will never execute. For example, this is how the optional chaining operator will be used with the newsletterForm variable.

newsletterForm?.addEventListener("submit", (e) => {});
Enter fullscreen mode Exit fullscreen mode

We are now sure that even if newsletterForm returns null, the event listener code will never execute, which makes our code more secure. Let's now enter the code of the event listener

TypeScript Error on FormInput

There is an issue with the if statement, because TypeScript is showing a red dashed line on the formInput variable, why is that?, because it is not sure what formInput is, you can use optional chaining to solve this. The next problem we have is trying to access the value property, because TypeScript is interpreting formInput to be of the type Element, and the Element type doesn't have a value property on it, so we also have to tell TypeScript that formInput is an HTMLInputElement, not an Element.

const formInput = document.querySelector(".newsletter__input") as HTMLInputElement;

newsletterForm?.addEventListener("submit", (e) => {
  e.preventDefault();

  let status = true;

  if (formInput?.value == "") {
    ...
  }
});
Enter fullscreen mode Exit fullscreen mode

We also need to fix the formError variable because typeScript is complaining that it isn't sure what formError is. In this case, we first need to check if formError does not return null before changing the innerHTML value because you can't do assignment operations with optional chaining, why is that?, there is a chance the variable won't exist, so what is the point of assigning it a value?, makes sense to me.

newsletterForm?.addEventListener("submit", (e) => {
  e.preventDefault();

  let status = true;

  if (formInput?.value == "") {
    status = false;

    formInput?.classList.add("newsletter__input--error");

    if (formError) {
      formError.innerHTML = "Email is required";
    }
  }

  if (formInput?.value != "" && formInput?.value.match(emailRegex) === null) {
    status = false;

    if (formError) {
      formInput?.classList.add("newsletter__input--error");
      formError.innerHTML = "Invalid email address";
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Next we go to the other event listener that listens for when the user types into the input box

formInput.addEventListener("input", () => {
  if (formInput.classList.contains("newsletter__input--error")) {
    formInput.classList.remove("newsletter__input--error");
    formError.innerHTML = "";
  }
});
Enter fullscreen mode Exit fullscreen mode

This is the same issue we have already solved before by using the optional chaining operator, and checking if formError exists before setting the innerHTML value

formInput?.addEventListener("input", () => {
  if (formInput.classList.contains("newsletter__input--error")) {
    formInput.classList.remove("newsletter__input--error");

    if (formError) {
      formError.innerHTML = "";
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

I will also do the same thing for the dismiss button I added an event listener to

dismissBtn?.addEventListener("click", () => {
  newsletter?.classList.remove("d-none");
  subscriptionMessage?.classList.add("d-none");
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

Rebuilding this project with TypeScript has taught me about Type Assertion, optional chaining and how to declare types for HTML Elements, I hope you also got to learn something from my experience in rebuilding this Front End Mentor project with TypeScript. If there is anything you will like to suggest that will help improve this article, I am opened to feedback. You can follow me on X and Linkedin.

Top comments (0)