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