React using Typescript.

Sun Mar 19 2023
6 min read
article cover

Introduction

React and typescript is a combination that is so powerful that you once try it, you will not want to switch back to plain old JavaScript. Typescript brings a lot of features to the table that it becomes your new default in no time.

But you may not feel the same the first time you write React code using typescript, and you will probably be frustrated. Typescript will feel like it is getting between you and writing code, but as you get used to writing type-safe react code you will feel, “Why wasn’t I using Typescript before?”.

This article will get you familiar with writing react code using typescript.

Setup

So let’s start by setting up our project. We will be using vite to scaffold our project, which by the way if you do not know is one of the fastest if not the fastest ways to create a React + Typescript project.

Use below command

npm create vite@latest project-name

And then select Typescript

Or you can do it in one line like this

npm create vite@latest project-name -- --template react-ts

Your folder structure will look like this

📦root-directory
 📂public              # Contains all the static files available to the public
 📜vite.svg
 📂src                 # All the code files
 📂assets
 📜react.svg
 📜App.css
 📜App.tsx           # Project index/start file
 📜index.css
 📜main.tsx          # React root file
 📜vite-env.d.ts
 📜.gitignore
 📜index.html
 📜package.json        # Project dependencies
 📜tsconfig.json       # Typescript configuration
 📜tsconfig.node.json  # Typescript configuration
 📜vite.config.ts      # Vite configuration

For example purpose, we will be making a registration page and a custom hook to give you a gist of how you can use typescript in your react project

We will also use TailwindCSS to style our app quickly

Components

So let’s start by making a wrapper component Layout. To create a typed component, we need to tell typescript what our component accepts as props and what are its types

Our Layout component accepts children prop, which is basically passing elements inside it, our Layout component will look like this

import React, { ReactNode } from "react";

interface LayoutProps {
  children: ReactNode;
}

const Layout = ({ children }: LayoutProps) => {
  return (
    <div className="m-auto max-w-lg">
      <nav className="flex justify-between p-4">
        <div>Home</div>
        <div>Login</div>
      </nav>
      {children}
      <footer className="mt-16">Footer</footer>
    </div>
  );
};

export default Layout;

Here we have declared our prop children as ReactNode which is a type that comes with react library, then we will write the rest of the component pass children between nav and footer elements.

But this was a really simple example, let’s look at a little complex one

Now we will create a custom element that will be used instead of the default html input element, our components will also have a label element inside it. Our component will look like this

import React, { HTMLAttributes, InputHTMLAttributes } from "react";

interface InputI extends InputHTMLAttributes<HTMLInputElement> {
  id: string;
  label: {
    title: string;
    className?: string;
  };
  wrapper?: HTMLAttributes<HTMLDivElement>;
}

const Input = ({ className, wrapper, label, id, ...props }: InputI) => {
  return (
    <div {...wrapper}>
      <label className={"text-sm " + label.className} htmlFor={id}>
        {label.title}
      </label>
      <input
        className={"w-full rounded-md border-[3px] border-gray-300 p-2 pl-3 " + className}
        {...props}
      />
    </div>
  );
};

export default Input;

This might look overwhelming if you are new to typescript, but it actually is really simple let’s see one by one

First, we have declared interface for our component, basically what it does is tell typescript that our component accepts id, label, wrapper, and all the attributes that an input element have (i.e., extends InputHTMLAttributes<HTMLInputElement>).

It is important to give our wrapper prop HTMLAttributes<HTMLDivElement> type as it will give us autocomplete in our IDE when we are using this component

Change handler

You will need this when you are creating a form, as you need to pass a function to onChange event for input elements, let’s write the code for it

const onChange = (event: ChangeEvent<HTMLInputElement>) => {
  setForm({
    ...form,
    [event.target.name]: event.target.value,
  });
};

Here we are telling typescript that onChange function will get an argument event which will be of type ChangeEvent and this event is triggered by HTMLInputElement. By doing so we will get attributes of input element along with ChangeEvent inside our event argument.

useState

Giving a type/interface to useState hook is really simple, you can do it by letting it infer from the array/object while initializing like this

const [form, setForm] = useState({
  first_name: "",
  last_name: "",
  email: "",
});

or creating a type and assigning it to the hook like this

type formState = {
  first_name: "";
  last_name: "";
  email: "";
};

const [form, setForm] = useState<formState>({
  first_name: "",
  last_name: "",
  email: "",
});

This will make sure that you always pass the right keys in the state

useRef

Without typescript useref is not that helpful to us as it does not know for which element we are going to use it and what attributes that element accept. But by passing the type in useRef makes a lot of improvement in our DX, you can add type to useRef like this

const buttonRef = useRef<HTMLButtonElement>(null);

Here you will see I have initialized using null it is necessary as useRef only accepts an HTML element or null as the initial value, if you use undefined your IDE will throw an error.

Form submit handler

Now that we have all the elements of a form ready, let’s submit the form data to the server

const submitHandler = async (event: FormEvent<HTMLFormElement>) => {
  event.preventDefault();
  buttonRef.current?.setAttribute("disabled", "true");

  // api request to server
  await api
    .post("/submit", formData)
    .then((data) => {
      // reset form
      setForm({
        first_name: "",
        last_name: "",
        email: "",
      });
    })
    .catch((er) => {
      // do something
    });

  buttonRef.current?.removeAttribute("disabled");
};

Here FormEvent is giving us event attributes available to form submit event same as previous change handler function.

In conclusion, React and TypeScript make a powerful combination that can greatly enhance your development experience. By using TypeScript, you can catch errors earlier in the development process and write more scalable and maintainable code. While it may take some time to get used to writing type-safe code, the benefits are well worth it. Hopefully, this article has given you a good starting point for using React with TypeScript, and you are now ready to start building your own type-safe React applications.

Get the full source code of this project here