Telerik blogs
ReactT2_1200x303

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.

Prerequisites

This article assumes you have basic knowledge of JavaScript and React.

Benefits of Using TypeScript With React

Here are some of the advantages TypeScript provides when building React applications:

  • Static type checking and better IntelliSense: TypeScript type checks the code and flags an error before compiling the code.
  • Better readability and maintainability: With type definitions, the codebase becomes much more readable and maintainable.
  • Better support for JSX: TypeScript integrates well with IntelliSense and offers better code completion for JSX.
  • Fewer undefined errors: TypeScript reduces the likelihood of undefined errors occurring at runtime.

Setting Up a React-TypeScript Project

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.

React Typescript folder structure

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.

TypeScript Overview

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.

How To Create React Components With TypeScript

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.

Managing States With TypeScript

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;

Handling Events

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.

Overview of Some Other React Hooks With TypeScript

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.

Putting It All Together

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.

React TypeScript Todo app

Conclusion

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.


Ifeoma-Imoh
About the Author

Ifeoma Imoh

Ifeoma Imoh is a software developer and technical writer who is in love with all things JavaScript. Find her on Twitter or YouTube.

Related Posts

Comments

Comments are disabled in preview mode.