In this post, we’ll look at a few advantages of using TypeScript with React. Then, we’ll set up a React-TypeScript project and go through some of the fundamentals.
TypeScript is a typed superset of JavaScript that compiles to standard JavaScript. With TypeScript, you specify types for values in your code, allowing you to write code with more confidence.
Over the years, TypeScript has grown in popularity among developers and has been adopted by various libraries and frameworks. While some of these libraries and frameworks adopted TypeScript by default, React gives you the option to use TypeScript or not.
React is a JavaScript library that offers a declarative, component-based method for creating user interfaces. Combining TypeScript and React has shown to be an effective one, with TypeScript type checking the code to identify errors and improve the quality of the entire development process, while React focuses on building the user interface.
In this post, we’ll look at a few advantages of using Typescript with React; then, we’ll set up a React-TypeScript project and go through some of the fundamentals.
This article assumes you have basic knowledge of JavaScript and React.
Here are some of the advantages TypeScript provides when building React applications:
There are many ways to add TypeScript to a React app, but the easiest and fastest is using create-react-app. It is an automatic CLI tool that will generate the file structure and automatically create all the settings files for our project. With create-react-app, we don’t have to manually set up a transpiler, bundler, etc. Instead, it handles all the configurations allowing us to focus on building our application.
Run the following command to set up a React-TypeScript project using create-react-app:
npx create-react-app react-typescript-demo --template typescript
This executes the create-react-app command and sets the template flag to typescript
. We also added a name for the project—react-typescript-demo
, but you can use any other name you choose.
Next, open the newly created folder in your preferred code editor by running cd react-typescript-demo
. The project’s folder structure should look like the one displayed below.
If you’ve dealt with React.js previously, this should be no surprise. The structure is quite similar; the files and folder serve the same purposes as when working with plain React, except that some application files with .js
or
.jsx
extensions are now replaced with .ts
or .tsx
instead.
Another difference compared to a simple React application is the addition of a tsconfig.json
file to the root level of the application. The file contains the required TypeScript configuration. The default configurations for this file are
sufficient for us, so we don’t need to change anything.
Before we proceed, let’s quickly go over the concept of types in TypeScript. Declaring a variable in JavaScript is straightforward, as shown below:
let name = "john";
let age = 12;
JavaScript automatically detects what the type of a variable may be. However, in TypeScript, you have to explicitly define the types of variables or objects to be used in your application.
Let’s take a look at how to define types in TypeScript.
let name: string;
let id: number;
let inSchool: boolean;
let friends: string[];
let age: number | string; //age can either be a number or a string
let bestFriend: [string, number]; //the array takes two elements of type string and number, respectively.
TypeScript adopts a strict type-checking mechanism and flags an error when any type definition has defaulted.
We also define types for objects in TypeScript as shown below:
// define type for an object
type Product = {
name: string,
id: number,
color?: string, // adding the "?" symbol after the property name makes the color property optional
isAvailable: boolean,
};
// define the object
let myProduct: Product = {
name: "Aaron's product",
id: 1,
color: "red",
isAvailable: true,
};
let products: Product[]; // define an array of products
TypeScript also allows you to define interfaces for complex type definitions.
interface Person {
name: string;
age: number;
}
One of the key differences between an interface definition and a type definition is that we cannot add new properties to a type, while an interface, on the other hand, can be extendable.
Also, unlike an interface, the type alias can be used for other types, such as primitives, while an interface declaration is always an object.
This article focuses on how to get started with TypeScript and React, so we won’t dive further into some other fundamental TypeScript. You can, however, visit the official TypeScript documentation to learn more.
React adopts a component-based approach to building user interfaces. With TypeScript, we can define both function and class-based components. We will exclusively use the function method in this article to create React components.
The syntax to create a simple React function component with TypeScript is similar to when you use plain React.
import React from "react";
function App() {
return <div className="App">Hello World</div>;
}
export default App;
Here, the function is plain and straightforward and doesn’t depend on any other external entity, so we didn’t have to provide types for it manually. TypeScript automatically detects that the return type of our component is a JSX Element.
On the other hand, if the component accepts props, we have to declare a type or an interface to define the form of the props object. Shown below is how to add some props to the App component.
import React from "react";
type appProps = {
name: string,
age?: number, //optional prop
};
function App({ name, age }: appProps) {
return (
<div className="App">
<h2>{name}</h2>
<p>{age}</p>
</div>
);
}
export default App;
In the code above, we defined a name
prop of type string
and an optional age
prop of type number
.
A component can also accept functions as props, as shown below:
import React from 'react';
type appProps = {
name: string,
age?: number // optional prop
makeNoise: () => void; // "void" because it doesn't return anything
}
function App({ name, age, makeNoise }: appProps) {
return (
<div className="App">
<h2>{name}</h2>
<p>{age}</p>
</div>
);
}
export default App;
Another way to type React function components with TypeScript is to import the FunctionComponent
or React.FC
from the React library, which provides a slightly different approach to creating function components.
import React, { FunctionComponent } from "react";
type appProps = {
name: string,
age?: number, // optional prop
};
export const App: FunctionComponent<appProps> = ({ name, age }) => {
return (
<div className="App">
<h2>{name}</h2>
<p>{age}</p>
</div>
);
};
This approach uses a generic type. Parameters of our function are inferred from the generic FunctionComponent
. The FunctionComponent approach is discouraged, with detailed reasons outlined here.
If you are familiar with React, you should be aware that, for the most part, you can’t always add simple, stateless components to your application. You’ll need to define state variables at some point to hold various state values in your application’s components.
Below is how to define a state variable in a React-TypeScript project using the useState
hook.
import React, { useState } from "react";
function App() {
const [sampleState, setSampleState] = useState("Ifeoma Imoh");
return <div className="App">Hello World</div>;
}
export default App;
The state’s type is automatically inferred. You can also increase type safety by declaring what type a state accepts:
import React, { useState } from 'react';
function App() {
const [sampleState, setSampleState] = useState("Ifeoma Imoh");
//sampleStateTwo is a string or number
const [sampleStateTwo, setSampleStateTwo] = useState<string | number>("");
//sampleStateThree is an array of string
const [sampleStateThree, setSampleStateThree] = useState<string[]>([""]);
return (
<div className="App">
Hello World
</div>)
}
export default App;
In a React-TypeScript project, we can work around events like we do when working on a plain React project. In addition to that, we can also add type safety to events.
To use a particular event type, you have to import it specifically from the React module. Let’s see how it works.
import React, { MouseEvent } from "react";
export const App = () => {
const handleClick = (event: MouseEvent) => {
event.preventDefault();
console.log(event); //logs the event object to the console
};
return (
<div>
<button onClick={handleClick}>Click!!</button>
</div>
);
};
In the code above, we rendered a button
and added a listener that triggers the handleClick
function when clicked. In the handleClick
function, we set the type of the event to the default MouseEvent
imported from React.
There are other supported events, such as the ChangeEvent
, AnimationEvent
, FocusEvent
, FormEvent
and so on. You can check out this link to see a complete list of all other supported event types.
Hooks were added in React version 16.8, enabling the use of state and other React features in functional components.
Let’s take a look at the useEffect
hook. This hook is used to handle side effects such as fetching data, setting timeouts, and other things that may require a lifecycle method.
Below is how to use the useEffect
hook in a React component.
import React, { useEffect } from "react";
export const App = () => {
useEffect(() => {
//do other stuffs
}, []);
return <div>Hello world</div>;
};
The effect definition is similar to how it is done traditionally in React, as it also doesn’t require any extra type configuration. TypeScript only checks to ensure that the syntax of the function (useEffect, etc.) is correct.
Another frequently used and important hook is the useRef React hook. The useRef
hook is described
in the React official documentation as a box that can hold a mutable value in the .current
property of its instance.
Below is how to set up a ref
in a React-TypeScript project.
import React, { useRef } from "react";
export const App = () => {
const divRef = useRef < HTMLDivElement > null;
//divRef.current contains a reference to the div tag rendered below
return <div ref={divRef}>...other nested elements</div>;
};
In the code above, we defined a ref
and set its type to HTMLDivElement
, i.e., it can work with only div
elements. You can visit this link to learn how to implement other React hooks with TypeScript.
Up to this point, we learned about some of the most important concepts needed to get started with building a React-TypeScript application. In this section, we’ll practice some of those concepts by building a simple to-do list application.
Return to the React-TypeScript project we created earlier and run the following command at the root level of the application to start the development server.
npm start
Next, open the App.tsx
file at the root level of your application and replace the code in it with the following:
import React, { useState, useRef, FormEvent, useEffect } from "react";
import { TodoComponent } from './components/Todo'
import "./App.css";
/*define the type for a Todo Object*/
export type Todo = {
id: number;
todo: string;
isDone: boolean;
};
function App() {
const [todos, setTodos] = useState<Todo[]>([]);
const [todo, setTodo] = useState<string>("")
const inputRef = useRef<HTMLInputElement>(null);
//effect to focus on the input field on load
useEffect(() => {
inputRef.current?.focus();
}, []);
//add a single todo to the todos array
const handleAdd = (event: FormEvent) => {
event.preventDefault();
if (inputRef.current?.value.length === 0) return;
setTodos((prevTodos) => [
...prevTodos,
{
id: Date.now(),
todo: `${inputRef.current?.value}`,
isDone: false,
},
]);
setTodo("")
};
//delete todo with a given id from the array
const deleteTodo = (id: number) => {
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
};
//toggle the isDone property of a given todo
const toggleDone = (id: number) => {
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === id ? { ...todo, isDone: !todo.isDone } : todo
)
);
};
return (
<div className="App">
<h1>React-TypeScript To-do list</h1>
<form onSubmit={handleAdd}>
<input type="text" ref={inputRef} onChange={(e) => setTodo(e.target.value)} value={todo} />
<button type="submit">Add New</button>
</form>
<ul className="todos">
{todos.map((todo, key) => (
<TodoComponent
key={key}
id={todo.id}
todo={todo.todo}
isDone={todo.isDone}
deleteTodo={deleteTodo}
toggleDone={toggleDone}
/>
))}
</ul>
</div>
);
}
export default App;
In the code above, we used concepts covered earlier in this article to create a basic to-do list application. The application renders a form, which, when filled and submitted, triggers the handleAdd
function and appends a new todo
object to the todos
array. We also created a deleteTodo
and toggleDone
function to delete and toggle the completion of a todo with the given id, respectively.
We also imported a component called TodoComponent
that we’re yet to create, and it renders individual to-do items. Now create a folder called components
in the src
directory, and inside it, create a file
called Todo.tsx
and add the following to it:
import React from "react";
import { Todo } from "../App";
/* create the prop types by merging the Todo types and other expected props from the App component */
type TodoProps = Todo & {
deleteTodo: (id: number) => void,
toggleDone: (id: number) => void,
};
//define the Todo Component
export const TodoComponent = ({
id,
todo,
isDone,
deleteTodo,
toggleDone,
}: TodoProps) => {
return (
<li
className={isDone ? "todo done" : "todo"}
onClick={() => toggleDone(id)}
>
<div>
<p>{todo}</p>
</div>
<button onClick={() => deleteTodo(id)}>delete</button>
</li>
);
};
In the code above, we’re using the &
operator to merge the Todo
types and the two extra function props (deleteTodo
and toggleDone
). The TodoComponent
renders a single to-do
item.
We also need to style the application. Replace the predefined styles in your src/App.css
file with the styles in this codeSandbox link:
Now you can save the changes and test the application in your browser.
This article covers the foundational knowledge and some of the most used concepts you need to start your React-TypeScript journey. However, you can learn more about TypeScript from its official documentation and up-to-date information on its integration with React.
Next up, you may want to check out Using TypeScript With React in a Large Code Base: Challenges and Tips.