DEV Community

Antoine Muller
Antoine Muller

Posted on

Introduction to typescript with React

In this article, I would like to share with you how I use typescript to improve my react code.
First we are going to see what is typescript and why it is used ?
Then, how we can use typescript with react ( components, hooks, extern libs ).
Finally, I will sum up what is the pros and cons using typescript in a react app .

Typescript ?

A common critic about javascript is that javascript is untyped. It means that you can do:

    let a = "Hello";
    let b = 5;

    // Here we substract a number to a string
    // Javascript does not warn us even if we try to substract a number to a string
    let c = a - b;

    console.log(a) // Hello
    console.log(b) // 5
    console.log(c) // NaN
Enter fullscreen mode Exit fullscreen mode

As you can see javascript is very permissive which can lead to unexpected behaviors and bugs.
Another recurrent critic is that, we don't know fields of objects in javascript.
Sometime we get an object and we are not sure what is the structure of this object.
For example:

const user = {
    firstName: "Eikichi",
    lastName: "Onizuka"
}

console.log(user.name)
// user.name does not exist.
// Javascript is unable to tell us field name does not exist
Enter fullscreen mode Exit fullscreen mode

These 2 samples can potentially provide error at runtime. It would be great to have hints before trying the code about potential error like ones above.
Typescript tries to address those issues by adding types to javascript.
Typescript is a programming language. By adding types, typescript is able to give some hints before running the code.
Typescript is not executed in the browser directly, typescript is first transformed into javascript code.
In the end, only javascript is executed in the browser when using typescript.

Now, let's see how we can use typescript alongside React !

The project

I am going to use some code sample from a basic todo list app using react and typescript.
In the app, we can add todo and toggle todo to make them done.
Todos will have 3 fields:

The project is created using create react app.
Create react app provides a template using react and typescript to get started quickly.
The goal of the project is to provide some react/typescript example in a little project. Styling is not important.
You can find the code of the project here.
Here is a screenshot of the todos app:

todos preview

React with Typescript

In this part we will see how we can use typescript with:

  • components
  • hooks
  • external libs

Shared types

Usually, there are types you need in several parts of your application. For example, a Todo type might be used in several components.
I define these types in a types.ts file at the root of the project. This way, we can access shared types easily across the app.
To define types, we use the interface keyword in typescript. Let's analyze how it's done in the todo app!

Todo app

As I said in the previous section todos have the following fields :

  • id: uuid. A uuid is a 128 bit number.
  • label: The label corresponding to the todo. This is represented as a string in our app.
  • isDone: A boolean.

Let's see how we can define the Todo type to use it later in our react app.
As I said before, all shared types are in the types.ts file.
Here is a sample of types.ts:

interface Todo {
    id: string
    label: string
    isDone: boolean
}
Enter fullscreen mode Exit fullscreen mode

We named this new type Todo.
Finaly we assign fields with their respective types:

  • id : string, uuid will be represented as a string (example: "123e4567-e89b-12d3-a456-426614174000")
  • label: string, the label will be represented as a string (example: "Cook")
  • isDone: boolean (example: true)

Great ! We have our Todo interface. We can now use it in the code like this:

let todo: Todo = {
    id: "123e4567-e89b-12d3-a456-426614174000",
    label: "Cook",
    isDone: false
}
Enter fullscreen mode Exit fullscreen mode

As you can see, we can specify the type of a variable using : in typescript.
If we try to access or to add a non present field, typescript will display an error.

We also need a NewTodo type. This type will be used to add a new todo in the list.
It is the same type as the Todo above except it does not have an id yet.
Here is the code behind in types.ts:

export interface NewTodo {
    label: string
    isDone: boolean
}
Enter fullscreen mode Exit fullscreen mode

We can now use our todos types inside components.
Let's see how we can organize our components!

React components

In React components, I like to define a Props interface before declaring the component.
This Props interface contains every property of the component.

In my opinion, here are the pros of writing the Props interface :

  • It forces us to think about what properties our component needs
  • If you open the file, you can quickly find out the parameters of the component ( you don't have to look at the component code to know what parameters it can take )
  • When we use the component in our App, typescript can warn us if we pass wrong parameters to our component.

Let's see a concrete example from the todo app!

Todo app

We are going to analyze the TodosList component. Its role is to display a list of todos.
It takes 2 parameters:

  • todos: This is the list of todos that will be displayed.
  • onTodoClick: A callback called when a todo is clicked. This callback takes a todo as a parameter.

Let's see how we can define this React component with typescript.

import { Todo } from './types'; // import the Todo type
import TodoItem from './TodoItem'; // TodoItem is the component used to display one todo on the screen

/*
 * We define our Props type
 * It is used to define the props our TodosList will take in parameter
 */
interface Props {
    todos: Array<Todo>,
    onTodoClick?: (todo: Todo) => void
}

/*
 * The TodosList component.
 * We are using our Props type to tell typescript "This component uses the Props type for its parameter".
 * This way, when we use our component, typescript is able to tell you if we try to use a non existing property. 
 * Or if we try to give a bad type to a props.
 */
function TodosList({todos, onTodoClick}: Props) {
    /* 
     * Now we can use todos and the onTodoClick
     * if we try to write : `todos.foo`, typescript can tell us that an array of todos has no "foo" property
     * Same things apply to onTodoClick. If we try to call onTodoClick like this : onTodoClick(10)
     * Typescript is able to say "10 is not a todo, onTodoClick takes a todo as a parameter not a number"
     */
    return (
        <ul>
            { todos.map(todo => <TodoItem key={todo.id} onTodoClick={onTodoClick} todo={todo} />) }
        </ul>
    )
}

export default TodosList
Enter fullscreen mode Exit fullscreen mode

Note: You can notice we have added a "?" to onTodoClick. It means that onTodoClick is optional.

Let's see what happens now if we try to use our component in another file:

/* Typescript warns us, because hello does not exist as a parameter for our TodosList */
<TodosList hello={"world"} /> 

/* Typescript warns us, because badTodos are missing id and label. */
let badTodos = [{isDone: false}, {isDone: true}];
<TodosList todos={badTodos} />
Enter fullscreen mode Exit fullscreen mode

As you can see, typescript can help us avoid bugs before running the code.
You can find another example of a component in the TodoItem.tsx file.

Now let's see how we can use typescript with hooks!

Hooks

There is several hooks. I will focus on useState for this article.
The useState hook enables us to keep a state in our component.
With typescript we can define what state we want to store with useState.
Typescript will then use this information to prevent us from setting a state with a wrong type.
Let's see an example :

/*
 * Typescript now knows that num is a number and setNum takes a number as a parameter.
 * Typescript will warn us if we try to call setNum("a"), for example.
 */
const [num, setNum] = useState<number>();
Enter fullscreen mode Exit fullscreen mode

Let's see an example in the todo App!

Todo app

In the todo app, we need the useState hook to manage todos.

Let's see the App.tsx code :

import styles from './App.module.css';
import {v4 as uuidv4} from 'uuid';
import { Todo, NewTodo } from './types';
import { useState } from 'react';
import TodosList from './TodosList';
import AddTodo from './AddTodo';

function App() {

  /*
   * With useState<Todo[]>, typescript knows: 
   * - todos is an Array of todos 
   * - setTodos takes an array of todos as parameter
   */
  const [todos, setTodos] = useState<Todo[]>([
    {id: uuidv4(), label: "Cleaning", isDone: true},
    {id: uuidv4(), label: "Cooking", isDone: false}
  ])

  function toggleTodo(todo: Todo) {
      setTodos(todos.map(
          t => t.id === todo.id ? {...t, isDone: !t.isDone} : t
      ))
  }

  function addTodo(newTodo: NewTodo) {
    /*
     * If we try to pass a non todos array, typescript will tell us
     */
    setTodos([...todos, {
        ...newTodo,
        id: uuidv4()
    }])
  }

  return (
    <div className={styles['App']}>
    {/* Since useState is typed, typescript knows that we are passing a todos array in TodosList */}
        <TodosList onTodoClick={toggleTodo} todos={todos} />
        <AddTodo onNewTodoSubmit={addTodo} />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Since useState is typed, typescript makes sure, we are not using todos and setTodos wrongfully.

Notice that we use an external library ( uuid ) for generating todo ids.
By default typescript does not know the v4 function returns a string.
Let's see how we can help typescript understand external libraries !

External libraries

For external libraries, there are usually 3 scenarios:

  • The library is written in typescript. When this is the case, most of the time we just need to npm install the library and we have types. It is the best case scenario.
  • The library does not ship directly with types. By default typescript does not know any types about the library. However, most of the time there is types written alongside the project. Usually, we can install those types using npm install @types/[LIB_NAME]. This is the case for react.For example, there is a @types/react package to add types with react.
  • The library is not written with typescript and there is no types. This is the worst case scenario. You have to either write types yourself or use the any types in typescript.

Note: As typescript is getting more and more popular, most of the time you will find types when using external libraries

Todo app

Let's get back to the uuid package. uuid package is not written in typescript.
However, there is a @types/uuid package for it. The package is installed using npm install --save-dev @types/uuid.
This way when we assign an uuid to a todo's id, typescript knows, we are assigning a string to id.

Conclusion

In my opinion, here are the pros and cons of using typescript with react.

Pros :

  • By writing types when we writing components it forces us to think a more about our components and how it should be used
  • If you have a compatible editor, typescript can give you error and autocompletion when you write code (even in JSX !)
  • When you use or open a component file, you can see easily its parameters. You don't have to ask yourself "what is the name of this property, or if this property takes a string or a number"

Cons :

  • It makes the code a bit more verbose. Since we need to specify types.
  • It adds some complexity to build the project. We now need to transform typescript to javascript before running the apps. Hopefully tools like cra provide a ready to use react/typescript template

As I said, you can find the code of the todo app in this repo.
I hope you like this little introduction to typescript with react! :)

Discussion (3)

Collapse
kernelsoe profile image
Kernel Soe

Thanks for this awesome guide!

I would like ask one quick question => is the same as declaring Array<TypeName> vs TypeName[] in an interface?

Collapse
towaanu profile image
Antoine Muller Author

Hi, happy you enjoyed the guide :) !

Yes Array<T> and T[] means the same thing.
For example Array<number> or number[] means an array of number in typescript.

You can find more about it in the official documentation : typescriptlang.org/docs/handbook/2...

Collapse
fera2k profile image
Fernando Vieira

Nice intro to Typescript. Thanks for sharing.