DEV Community

Cover image for Typescript - For React Developers
Junaid
Junaid

Posted on

Typescript - For React Developers

Lets get React Developers armoured with TS

Introduction
React is a popular JavaScript library used for building user interfaces. It is a great tool for creating dynamic and responsive web applications. However, as your application grows in complexity, it can become harder to maintain and debug. This is where TypeScript comes in.
TypeScript is a static typing language that can be used with React to improve code quality and maintainability. In this post, we will discuss the benefits of using TypeScript in React and how to get started with it.

Benefits of Using TypeScript in React
Here are some key benefits of using TypeScript in React:

Type Safety

  • One of the main benefits of using TypeScript with React is type safety. TypeScript provides a way to catch errors early in development by providing type checking for components, props, state, and other variables. This helps catch errors before they become bigger issues, making your code more reliable and easier to maintain.

Improved Code Readability and Maintainability

  • TypeScript provides better documentation for your code and helps ensure consistency across your project. This improves the readability and maintainability of your codebase, making it easier for developers to understand and modify your code.

Better Tooling Support

  • TypeScript integrates well with many popular development tools, including IDEs, linters, and testing frameworks. This makes it easier to work with, and ensures that your code is consistent and follows best practices.

Faster Development and Easier Refactoring

  • TypeScript provides better autocompletion and refactoring support, making it easier to write and modify code. This results in faster development times and a more efficient workflow.

Getting Started with TypeScript in React

  • To use TypeScript in a React project, you need to add a few dependencies to your project and configure your development environment. Here are the steps to get started:

Step 1: Install Dependencies

  • If you are starting a fresh React App here is the template that will configure typescript for you directly
//using npx
npx create-react-app my-app --template typescript
// using yarn
yarn create react-app my-app --template typescript
Enter fullscreen mode Exit fullscreen mode
  • If you already have a react app and you want to add Typescript support now , here is the code:
//using npm
npm install --save typescript @types/node @types/react @types/react-dom @types/jest

//using yarn
yarn add typescript @types/node @types/react @types/react-dom @types/jest
Enter fullscreen mode Exit fullscreen mode

This will install TypeScript and the necessary type definitions for React and ReactDOM.

Step 2: Use TypeScript in Your Code

Now that your project is configured, you can start writing components and other code using TypeScript syntax. Here are some examples:

Defining Types for Props and State

import React from 'react';

interface Props {
  name: string;
  age: number;
}

interface State {
  isOn: boolean;
}

class MyComponent extends React.Component<Props, State> {
  state: State = {
    isOn: false,
  };

  handleClick = () => {
    this.setState((prevState) =>

Enter fullscreen mode Exit fullscreen mode

Before we go any further here are some basics for typescript you need to know
Basic Types

// Defining variables with types
let age: number = 30;
let name: string = "John Doe";
let isStudent: boolean = true;
let hobbies: string[] = ["reading", "swimming", "traveling"];

// Defining functions with types
function greet(name: string): void {
  console.log(`Hello, ${name}!`);
}

function add(x: number, y: number): number {
  return x + y;
}
Enter fullscreen mode Exit fullscreen mode

Interface

An interface is a TypeScript feature that allows you to define the shape of an object or class. Interfaces are often used for describing the structure of objects that will be passed as arguments or returned from functions. Here's an example of how to define an interface in TypeScript:

interface Person {
  name: string;
  age: number;
  email: string;
}
Enter fullscreen mode Exit fullscreen mode
  • In this example, we define an interface called Person that has three properties: name of type string, age of type number, and email of type string.

  • Interfaces are often used for describing the shape of objects that will be passed around in your code. They can be extended and combined to create more complex types.

Type

A type is another TypeScript feature that allows you to define custom types. Types are often used for defining union types, intersection types, and other complex types. Here's an example of how to define a type in TypeScript:

type Car = {
  make: string;
  model: string;
  year: number;
}

Enter fullscreen mode Exit fullscreen mode
  • In this example, we define a type called Car that has three properties: make of type string, model of type string, and year of type number.

  • Types are often used for defining more complex types that cannot be described by interfaces. They can also be used for defining union types, intersection types, and other advanced types.

Differences
The main differences between type and interface in TypeScript are:

  • Interface can be extended to create new interfaces, while type can be used to define union types, intersection types, and other complex types.
  • Type can be used to define computed properties, while interface cannot.
  • Type is more flexible than interface in some cases, because it allows you to define more complex types that cannot be described by interfaces.

Union

In TypeScript, a union type is a type that can represent values of multiple types. You can use the vertical bar (|) to combine two or more types into a union type.

Here's an example of a union type in TypeScript:

type MyType = string | number;
Enter fullscreen mode Exit fullscreen mode

In this example, MyType is a union type that can represent values of either string or number.

You can use union types in various scenarios to make your code more flexible. For example, you might want to define a variable that can accept either a string or a number value.

Typing Arrays

To define an array type, you can use the following syntax:

let myArray: Type[];
Enter fullscreen mode Exit fullscreen mode

Here, Type is the type of elements that the array can hold.

For example, if you want to define an array of strings, you can do the following:

let myArray: string[];
Enter fullscreen mode Exit fullscreen mode

Now, myArray can only hold string values.

You can also define an array of union types by using the following syntax:

let myArray: (string | number)[];
Enter fullscreen mode Exit fullscreen mode

Now, myArray can hold values of either string or number type.

Typing Objects
To define an object type, you can use the following syntax:

let myObject: { key1: Type1, key2: Type2, ... };
Enter fullscreen mode Exit fullscreen mode

Here, Type1 and Type2 are the types of values that the keys key1 and key2 can hold. You can define as many key-value pairs as you want.

For example, if you want to define an object that has a name property of type string and an age property of type number, you can do the following:

let myObject: { name: string, age: number };
Enter fullscreen mode Exit fullscreen mode

You can also define an object with dynamic keys using the following syntax:

let myObject: { [key: string]: Type };
Enter fullscreen mode Exit fullscreen mode

Here, key is the name of the dynamic key, and Type is the type of values that the key can hold. This syntax allows you to define an object with any number of keys, as long as the key names are strings.

Generics:

Generics are a powerful feature in TypeScript that allow you to create reusable functions, classes, and interfaces that can work with a variety of types. The basic syntax for generics is to define a type parameter in angle brackets <>, which can be used throughout the function, class, or interface. Here's an example of a generic function that returns the first element of an array:

// Defining a generic function that works with any type
function identity<T>(arg: T): T {
  return arg;
}

// Using the identity function with a string
const result1 = identity<string>("hello"); // result1 is "hello"

// Using the identity function with a number
const result2 = identity<number>(42); // result2 is 42
Enter fullscreen mode Exit fullscreen mode

Using Typescript in React
Now that we have the basics all known to us , lets see how we can put typescript magic in react and make it typesafe

User Events

Using Typescipt for giving types to different user events in React

Before we start giving types to the events, we need to import the React types. These can be imported as follows:

import React, { ChangeEvent, MouseEvent } from 'react';
Enter fullscreen mode Exit fullscreen mode

Here, we are importing the ChangeEvent and MouseEvent types for use in our component.

Giving types to events

  • onClick The onClick event is used to handle mouse clicks on an element. Here's an example of how to give proper types to the onClick event:
function handleClick(event: MouseEvent<HTMLButtonElement>) {
  console.log('Button clicked');
}

function MyButton() {
  return <button onClick={handleClick}>Click me</button>;
}
Enter fullscreen mode Exit fullscreen mode

In this example, we give the handleClick function a type of MouseEvent. This means that the function expects a MouseEvent object, with the target element being a button element.

onChange
The onChange event is used to handle changes in the value of an input element. Here's an example of how to give proper types to the onChange event:

function handleChange(event: ChangeEvent<HTMLInputElement>) {
  console.log('Input value changed:', event.target.value);
}

function MyInput() {
  return <input onChange={handleChange} />;
}
Enter fullscreen mode Exit fullscreen mode

In this example, we give the handleChange function a type of ChangeEvent. This means that the function expects a ChangeEvent object, with the target element being an input element.

onSubmit

The onSubmit event is used to handle form submissions. Here's an example of how to give proper types to the onSubmit event:

function handleSubmit(event: FormEvent<HTMLFormElement>) {
  event.preventDefault();
  console.log('Form submitted');
}

function MyForm() {
  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we give the handleSubmit function a type of FormEvent. This means that the function expects a FormEvent object, with the target element being a form element.

Read More About available types here

Typing State in React

When building React components, it's often useful to define the shape of the component's state. This can help catch type-related errors at compile time, making your code more robust and easier to maintain. Here's how to type the state of a React component using TypeScript:

import React, { useState } from 'react';

interface State {
  count: number;
  message: string;
}

function MyComponent() {
  const [state, setState] = useState<State>({
    count: 0,
    message: 'Hello, world!',
  });

  const handleClick = () => {
    setState((prevState) => ({
      ...prevState,
      count: prevState.count + 1,
    }));
  };

  return (
    <div>
      <div>{state.message}</div>
      <div>Count: {state.count}</div>
      <button onClick={handleClick}>Increment count</button>
    </div>
  );
}

export default MyComponent;
Enter fullscreen mode Exit fullscreen mode

In this example, we define an interface called State that has a count property of type number and a message property of type string. We use the useState hook to define the component's state, and pass in an initial state object of type State. We then use the handleClick function to update the state by incrementing the count property. We can access the state properties using state.count and state.message.

The as operator

The as operator in TypeScript is used for type assertions. It allows you to explicitly cast a value to a specified type, telling the TypeScript compiler to treat the value as if it were of that type.

const value: unknown = "Hello, world!";

// Error: Property 'toUpperCase' does not exist on type 'unknown'.
value.toUpperCase();

// Using the 'as' operator to assert that 'value' is a string.
const valueAsString = value as string;

// No error: 'valueAsString' is now treated as a string by the TypeScript compiler.
valueAsString.toUpperCase(); // "HELLO, WORLD!"

Enter fullscreen mode Exit fullscreen mode

The as operator comes very handy in a lot of places when you are dealing with typescript in react

Utility Types in Typescript

  • Utility types in TypeScript are a set of built-in type transformations that can help you create new types based on existing ones. These types are defined in the utility-types module, which is included with TypeScript.

    • Here are some common utility types:

Partial: This utility type creates a new type that has all the properties of T, but with each property being optional. For example:

interface Person {
  name: string;
  age: number;
}

type PartialPerson = Partial<Person>;

// Equivalent to:
// type PartialPerson = {
//   name?: string;
//   age?: number;
// };

Enter fullscreen mode Exit fullscreen mode

Required: This utility type creates a new type that has all the properties of T, but with each property being required.
This is just the opposite of Partial

Pick: This utility type creates a new type by picking a set of properties of type K from T

interface Person {
  name: string;
  age: number;
  address: string;
}

type PersonNameAndAge = Pick<Person, "name" | "age">;

// Equivalent to:
// type PersonNameAndAge = {
//   name: string;
//   age: number;
// };
Enter fullscreen mode Exit fullscreen mode

Exclude: This utility type creates a new type by excluding all the properties of U from T.

type Fruit = "apple" | "banana" | "orange";
type NonBananaFruit = Exclude<Fruit, "banana">;

// Equivalent to:
// type NonBananaFruit = "apple" | "orange";

Enter fullscreen mode Exit fullscreen mode

There are other utility types also you can read about them here

Types of Components
In React, there are two main ways to define a functional component: using the React.FC type or returning a JSX.Element directly from the function.

// FC Type
import React from 'react';

type Props = {
  name: string;
};

const MyComponent: React.FC<Props> = ({ name }) => {
  return <div>Hello, {name}!</div>;
};

Enter fullscreen mode Exit fullscreen mode
//JSX.Element Type
import React from 'react';

type Props = {
  name: string;
};

function MyComponent({ name }: Props): JSX.Element {
  return <div>Hello, {name}!</div>;
}

Enter fullscreen mode Exit fullscreen mode

Both approaches are valid, but there are some differences to consider.

The React.FC approach has some advantages, such as automatically providing the children prop and making it easy to define default props and prop types. However, it also has some disadvantages, such as making it more difficult to define props that are required or optional.

On the other hand, returning a JSX.Element directly has the advantage of being more flexible and explicit about the component's return type. However, it also requires you to manually define default props and prop types, and it doesn't automatically provide the children prop.

Using Generics in React For Advanced Types

import React from 'react';

interface Props<T> {
  data: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>(props: Props<T>) {
  return (
    <div>
      {props.data.map((item) => props.renderItem(item))}
    </div>
  );
}

export default List;

Enter fullscreen mode Exit fullscreen mode

In this example, we define a generic type T that represents the type of data that the List component can render. The Props interface takes a generic type T and defines two properties:

data: An array of type T.
renderItem: A function that takes an item of type T and returns a ReactNode.
The List component takes a generic type T and renders the list of items using the props.data.map() method. The props.renderItem() function is called for each item in the list, and the resulting ReactNode is rendered in the output.

Here's how you can use the List component:

import React from 'react';
import List from './List';

interface Item {
  id: number;
  name: string;
}

function App() {
  const items: Item[] = [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' },
    { id: 3, name: 'Item 3' },
  ];

  const renderItem = (item: Item) => (
    <div key={item.id}>
      <h3>{item.name}</h3>
    </div>
  );

  return (
    <div>
      <List<Item> data={items} renderItem={renderItem} />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

There is a lot of other things that typescript offers , you can read more about best practices and use cases of typescript in react here

I hope with this knowledge you will be able to tackle most of the problems of typescript and ace your react code with typescript superpowers and make it bulletproof.

For more such tutorials ,React out to me on
linkedin
Also keep an eye on my website for amazing content coming in
jayshahcodes

Top comments (0)