Switching To Typescript in your React Apps

Switching To Typescript in your React Apps

Featured on Hashnode

Once you go Typescript, you don’t go back - A couple of Pretty Smart People I know.

These days more developers seem to be making the switch to Typescript. It is rightly so because typescript is a statically typed superset of Javascript that compiles to Javascript. It means that it contains all the features and functionality of JavaScript and more that help us build faster and more robust react apps.

The extra features of typescript include:

  • Strongly-typed arrays and Strongly-typed tuples
  • Type annotations: It allows us to assign types to variables.
  • The never type: It is used to represent a type of value that will never occur.
  • The unknown type: It allows us to reduce the use of any and create more strongly-typed code.
  • Intersection types: It allows us to combine existing types to form a new type with all the members from the type it is based on
  • Union types: It allows us to combine a new form of type using the pipe | character

Although Typescript helps reduce the chances of bugs, makes refactoring easier, improves the use of Javascript, self-documents code and helps with readability, the typing system is said to be complicated.

Setting Up TypeScript + React

A react project with Typescript can be created either manually or using the Create React App. Create React App is a CLI tool that saves one from time-consuming setup and configuration of React Apps. In this piece, I’ll be using Create React App(CRA).

Prerequisites

  • Npm and Node > 5.2 installed.
  • Create React App (CRA) > 2.1 installed.
  • Basic Knowledge of React.

Setting Up

To start a new Create React App project with TypeScript we need to run

npx create-react-app supercharged-javascript --template typescript
                        # or
yarn create react-app supercharged-javascript --template typescript

The “--template typescript” flag is added to generate a typescript template. A Typescript project called supercharged-javascript is created. You can name your project whatsoever you choose.

 cd supercharged-javascript 
 code .

You’ll notice that there are a couple of differences in the generated project template when compared to the regular react app template

image.png These differences are:

  • .tsx file - File extensions that were previously .js/jsx are now .tsx, tsx stands for TypeScript JSX.
  • A tsconfig.json file - According to the Typescript Documentation- “The tsconfig.json file specifies the root files and the compiler options required to compile the project”. It contains all Typescript configurations.
  • A react-app-env.ts - This file contains typescripts types definition and allows support for importing resources like bmp, gif, jpeg, jpg, png, webp, svg and CSS Modules.

Apart from these few, every other thing is pretty much the same. We also get the extra features Create React App comes saddled with like linting, hot reload and the likes.

Starting Our App

image.png At this point, when we run our react app, we should have the screen above. A screen we are probably used to, Seems like React + Javascript, doesn’t it? Well, that’s probably because it still is. As I mentioned earlier, Typescript still compiles to Javascript.

Creating Our Feedback Form

We will be creating a feedback form that contains two input fields and a text area field that will take the value of name, email and feedback, and a submit button. image.png

We will start by creating a Form.tsx file for our Form component in the src folder and add the code below which gives us a starting point for our form. In our Form.tsx we'll have:

import React from "react";

const Feedback= ({ userCredentials }) => {
  const { displayName } = userCredentials;
  return (
    <article >
      <div className="form-header">
        <h1>Thanks for filling the form {displayName}, We've received Your Feedback!</h1>
      </div>
    </article>
  )
}

const Form = () => {
  const [userCredentials, setCredentials] = useState({
    displayName: "",
    email: "",
    message: "",
  });
  const [showFeedback, setFeedback] = useState({
    showFeedbackComponent: false
  });
  const { email, displayName, message } = userCredentials;
  const { showFeedbackComponent } = showFeedback;
  const handleSubmit = (event) => {
    event.preventDefault();
    if (email && displayName && message) {
      setFeedback({ showFeedbackComponent: true })
    }
  };
  const handleChange = (event) => {
    const { value, name } = event.target;
    setCredentials({ ...userCredentials, [name]: value });
  };
  return (
    <div className="form">
      {
        showFeedbackComponent ?
          <Feedback
            userCredentials={userCredentials}
          /> :
          <section >
            <div className="form-header">
              <h1>Hey There, We Are Collectng Feedback!</h1>
            </div>
            <div className="form-content">
              <form onSubmit={handleSubmit}>
                <div className="form-group">
                  <label htmlFor="displayName">Name *</label>
                  <input
                    required
                    type="text"
                    name="displayName"
                    value={displayName}
                    onChange={handleChange}
                  />
                </div>
                <div className="form-group">
                  <label htmlFor="email">Email Address *</label>
                  <input
                    required
                    type="email"
                    name="email"
                    value={email}
                    onChange={handleChange}
                  />
                </div>
                <div className="form-group">
                  <label htmlFor="message">Feedback *</label>
                  <textarea
                    name="message"
                    className="form-input"
                    value={message}
                    onChange={handleChange}
                    required
                    rows={10}
                  />
                </div>
                <div className="form-group">
                  <button type="submit"
                  >Submit Feedback</button>
                </div>
              </form>
            </div>
          </section >
      }
    </div>
  )
}
export default Form;

This is a React feedback form without any Typescript functionality. Let's add our styles to our App.css file


html {
  width: 100%;
  height: 100%;
}
body {
  background: linear-gradient(45deg, rgba(66, 183, 245, 0.8) 0%, rgba(66, 245, 189, 0.4) 100%);
  color: rgba(0, 0, 0, 0.6);
  font-family: "Roboto", sans-serif;
  font-size: 14px;
  line-height: 1.6em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.form {
  padding: 3rem;
  z-index: 15;
  position: relative;
  background: #FFFFFF;
  width: 70%;
  border-radius: 4px;
  box-shadow: 0 0 30px rgba(0, 0, 0, 0.1);
  box-sizing: border-box;
  margin: 100px auto 10px;
  overflow: hidden;
}


.form-group {
  display: -webkit-box;
  display: flex;
  flex-wrap: wrap;
  -webkit-box-pack: justify;
  justify-content: space-between;
  margin: 0 0 20px;
}
.form-group:last-child {
  margin: 0;
}
.form-group label {
  display: block;
  margin: 0 0 10px;
  color: rgba(0, 0, 0, 0.6);
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: .2em;
}

.form-group input {
  outline: none;
  display: block;
  background: rgba(0, 0, 0, 0.1);
  width: 100%;
  border: 0;
  border-radius: 4px;
  box-sizing: border-box;
  padding: 12px 20px;
  color: rgba(0, 0, 0, 0.6);
  font-family: inherit;
  font-size: inherit;
  font-weight: 500;
  line-height: inherit;
  -webkit-transition: 0.3s ease;
  transition: 0.3s ease;
}
.form-group textarea {
  outline: none;
  display: block;
  background: rgba(0, 0, 0, 0.1);
  width: 100%;
  border: 0;
  border-radius: 4px;
  box-sizing: border-box;
  padding: 12px 20px;
  color: rgba(0, 0, 0, 0.6);
  font-family: inherit;
  font-size: inherit;
  font-weight: 500;
  line-height: inherit;
  -webkit-transition: 0.3s ease;
  transition: 0.3s ease;
}
.form-group input:focus {
  color: rgba(0, 0, 0, 0.8);
}

.form-group button {
  outline: none;
  background: #4285F4;
  width: 100%;
  border: 0;
  border-radius: 4px;
  padding: 12px 20px;
  color: #FFFFFF;
  font-family: inherit;
  font-size: inherit;
  font-weight: 500;
  line-height: inherit;
  text-transform: uppercase;
  cursor: pointer;
}

.form-header {
  margin: 0 0 40px;
}
.form-header h1 {
  padding: 4px 0;
  color: #4285F4;
  font-size: 24px;
  font-weight: 700;
  text-transform: uppercase;
}

Let's import our Form.tsx into our App.tsx:

image.png

We should get a ton load of errors.

image.png

To fix them we have to understand types and interfaces.

Types and Interfaces

Types

Typescript deals with types, when we declare variables we can specify their types. Javascript is known to have eight types: string, number, null, undefined, object, symbol, bigint, and boolean. However because Javascript is dynamically typed, it will not know the type of your variable until runtime and errors and bugs often come up. Typescript adds static types to the code and this helps in fixing the problem.

We declare variables with types this way :


let variableName: typeScriptType = value;
//string
let fullName: string = 'Bami Ogunfemi'  
//boolean
let done: boolean = true;
//number
let age: number = 19;

Interfaces

Interfaces in TypeScript are used when we need to give types to objects properties. Interfaces are used to declare our types We declare interfaces this way:

interface objName{
key1: typeScriptType,
key2 typeScriptType,
}

//person interface object
interface person{
name: string,
age: number,
happy: boolean
}

Types & Interfaces vs Prop Types Prop types are used in Javascript for type checking. To run type checking on the props for a component, you can assign the special propTypes property. As of React v15.5, React.PropTypes has moved into a different package.

Let's Create an Interface describing our data

interface userPropsType {
  displayName: string,
  email: string,
  message: string,
}
interface formProps{
  userCredentials: userPropsType,
}

Now let’s extend our app to use the interface we just created. Now our feedback component has access to our props.

import React, { useState, FC , ChangeEvent, FormEvent} from "react";

interface userPropsType {
  displayName: string,
  email: string,
  message: string,
}
interface formProps{
  userCredentials: userPropsType,
}
const Feedback: FC<formProps> = ({ userCredentials }) => {
  const { displayName } = userCredentials;
  return (
    <article >
      <div className="form-header">
        <h1>Thanks for filling the form {displayName}, We've received Your Feedback!</h1>
      </div>
    </article>
  )
}

const Form = () => {
  const [userCredentials, setCredentials] = useState({
    displayName: "",
    email: "",
    message: "",
  });
  const [showFeedback, setFeedback] = useState({
    showFeedbackComponent: false
  });
  const { email, displayName, message } = userCredentials;
  const { showFeedbackComponent } = showFeedback;
  const handleSubmit = (event) => {
    event.preventDefault();
    if (email && displayName && message) {
      setFeedback({ showFeedbackComponent: true })
    }
  };
  const handleChange = (event) => {
    const { value, name } = event.target;
    setCredentials({ ...userCredentials, [name]: value });
  };
  return (
    <div className="form">
      {
        showFeedbackComponent ?
          <Feedback
            userCredentials={userCredentials}
          /> :
          <section >
            <div className="form-header">
              <h1>Hey There, We Are Collectng Feedback!</h1>
            </div>
            <div className="form-content">
              <form onSubmit={handleSubmit}>
                <div className="form-group">
                  <label htmlFor="displayName">Name *</label>
                  <input
                    required
                    type="text"
                    name="displayName"
                    value={displayName}
                    onChange={handleChange}
                  />
                </div>
                <div className="form-group">
                  <label htmlFor="email">Email Address *</label>
                  <input
                    required
                    type="email"
                    name="email"
                    value={email}
                    onChange={handleChange}
                  />
                </div>
                <div className="form-group">
                  <label htmlFor="message">Feedback *</label>
                  <textarea
                    name="message"
                    className="form-input"
                    value={message}
                    onChange={handleChange}
                    required
                    rows={10}
                  />
                </div>
                <div className="form-group">
                  <button type="submit"
                  >Submit Feedback</button>
                </div>
              </form>
            </div>
          </section >
      }
    </div>
  )
}
export default Form;

We are now using the interface in our application. Next, we have to add the form event function. For handleSubmit, we will use the FormEvent with the type of HTMLFormElement.


const handleSubmit = (event:React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    if (email && displayName && message) {
      setFeedback({ showFeedbackComponent: true })
    }
  };

Now the handleSubmit expects an event of FormEvent and type of Form Element. The handleChange will take an event of HandleChange and expect type of HTMLInputElement for input and HTMLTextAreaElement for textarea.

 const handleChange = (event: React.ChangeEvent<HTMLInputElement> |React.ChangeEvent<HTMLTextAreaElement>) => {
        const { value, name } = event.target;
        setCredentials({ ...userCredentials, [name]: value });
      };

Our Form.tsx file should look like this now :


import React, { useState, FC, ChangeEvent ,FormEvent } from "react";
interface userPropsType {
  displayName: string,
  email: string,
  message: string,
}
interface formProps{
  userCredentials: userPropsType,
}
const Feedback: FC<formProps> = ({ userCredentials }) => {
  const { displayName } = userCredentials;
  return (
    <article >
      <div className="form-header">
        <h1>Thanks for filling the form {displayName}, We've received Your Feedback!</h1>
      </div>
    </article>
  )
}

const Form = () => {
  const [userCredentials, setCredentials] = useState({
    displayName: "",
    email: "",
    message: "",
  });
  const [showFeedback, setFeedback] = useState({
    showFeedbackComponent: false
  });
  const { email, displayName, message } = userCredentials;
  const { showFeedbackComponent } = showFeedback;
  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    if (email && displayName && message) {
      setFeedback({ showFeedbackComponent: true })
    }
  };
  const handleChange = (event: ChangeEvent<HTMLInputElement> |ChangeEvent<HTMLTextAreaElement>) => {
    const { value, name } = event.target;
    setCredentials({ ...userCredentials, [name]: value });
  };
  return (
    <div className="form">
      {
        showFeedbackComponent ?
          <Feedback
            userCredentials={userCredentials}
          /> :
          <section >
            <div className="form-header">
              <h1>Hey There, We Are Collectng Feedback!</h1>
            </div>
            <div className="form-content">
              <form onSubmit={handleSubmit}>
                <div className="form-group">
                  <label htmlFor="displayName">Name *</label>
                  <input
                    required
                    type="text"
                    name="displayName"
                    value={displayName}
                    onChange={handleChange}
                  />
                </div>
                <div className="form-group">
                  <label htmlFor="email">Email Address *</label>
                  <input
                    required
                    type="email"
                    name="email"
                    value={email}
                    onChange={handleChange}
                  />
                </div>
                <div className="form-group">
                  <label htmlFor="message">Feedback *</label>
                  <textarea
                    name="message"
                    className="form-input"
                    value={message}
                    onChange={handleChange}
                    required
                    rows={10}
                  />
                </div>
                <div className="form-group">
                  <button type="submit"
                  >Submit Feedback</button>
                </div>
              </form>
            </div>
          </section >
      }
    </div>
  )
}
export default Form;

Conclusion

In this article, we’ve covered setting up TypeScript in a React project, and why we might want to start using Typescript in our react apps. We’ve also built a feedback form with TypeScript and React to see how TypeScript is used in React projects. The supporting repository for this article is available on GitHub .

Resources

Typescript Handbook
Chibicode’s tutorial