TypeScript Learning Adventures: A Tale of Love and Hate - React and TypeScript

React JS is a very well-liked JavaScript library for creating incredible user interfaces. Additionally, it works with TypeScript. Although it's not the default, TypeScript is...

TypeScript Learning Adventures: A Tale of Love and Hate - React and TypeScript
Photo by Miltiadis Fragkidis / Unsplash

React JS is a very well-liked JavaScript library for creating incredible user interfaces. Additionally, it works with TypeScript.

Although it's not the default, TypeScript is used in the majority of significant React projects. In this post, you'll learn how it works.

Now, a brief disclaimer: I won't teach React's fundamentals in this article. This article is for you if you already know React and want to learn how to use TypeScript in React.

Project setup

To build a React app with Typescript, we need a project setup.

The setup should handle both our React code and TypeScript at the same time. For example, JSX can handle our React code by compiling it to JavaScript, while also optimizing it.

Lucky for us there is a CreateReactApp, a tool provided by the React team. We can use it to create React applications, and it supports TypeScript out of the box. Setting up such a project on our own can be somewhat challenging, so we will use CreateReactApp to save time.

There, you'll discover how to use CreateReactApp and TypeScript to start a new project or add TypeScript to an existing one. We'll create a new project in our case, so we'll paste in the following command:

npx create-react-app book-management-app --template typescript

It scaffolds a new project into this folder. And in that project, a React application is created so that we may all write our code using TypeScript.

You might see that it has the same structure as a React App you develop without TypeScript. But the tsconfig.json file is already visible. By the way, you can change this file to fit your requirements, which I explained in the previous article. The majority of use cases are covered by the default configuration.

We also have access to some .tsx files in the src folder, which is where we will write our source code. These .tsx files are here because these are files where you can write TypeScript code. But also you can write all the JSX code too. JSX is a special JavaScript syntax related to React. There you write HTML markup inside of your JavaScript. Or in this case, TypeScript code.

Now, we can already see some TypeScript syntax in these files. For instance, here, we have a type assignment in the index.tsx file:

const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);

And with that, we have a foundation upon which to build.

How does TypeScript work in React?

Our development server can be started with npm start command. We should leave it running so it can watch our files for updates.

It will also recompile the different sorts of scripts (.js, .ts, .tsx) into JavaScript. So if you save a file after modification, it will do so. Finally, your React application will be built and served on the local host on port 3000.

Website design books
Photo by Greg Rakozy / Unsplash

When you run the app, you won't see anything special. We are currently only rendering the default React logo in the App.tsx file, but let's change it to this:

import React from 'react';

const App: React.FC = () => {
  return (
    <div className="App">Hello world!</div>
  );
};

export default App;

This is regular JavaScript code. We see this type of assignment though, and there, what we can see is that we assigned this strange React.FC type to App. Now, if we ignore that type for a second, what's actually stored in App?

A function, yes, an arrow function, but it's still a function nonetheless.  Under the hood, this is a type made available by the react types package, which is now crucial.

If you navigate to the node_modules folder, you'll notice that there are many types there. As well as all the react-dom types here and in the @types folder. But what kind is this FC type, then?

This FC is shorthand for a football club.

Football before a footballmatch between two junior teams. Football or soccer is huge in Sweden and way fields are available for everyone is simple unique.

**Please add a link to https://danielnorin.com/ if you use the picture. Thanks!
Photo by Daniel Norin / Unsplash

Just kidding, it's not!

It's shorthand for a function component. You may use a longer version of this in its place. That is the same kind. The second one is a shortcut that states that the data we save in this App must be a function. But one that is eligible to be used as a function component in React.

Of course, you write class-based components as well. But this is a function component now since it produces JSX, which is how a function component in React is produced.

We will experience a problem if I remove this return statement. Because it would prevent the compilation from starting. Now that we've told TypeScript that we want to put a function component here, we would encounter an error. Because all that is stored is a regular function and not a function that returns JSX would be considered a react element.

As a result, this would be incorrect, and we would need to make it right. Thus, we can already observe how TypeScript enhances our project in this area. It provides extra type safety and ensures that we can't operate if, for instance, we build an incorrect component.

About the possibility that you would ever forget this return statement. Keep in mind that you are creating a larger component containing a variety of if statements and return statements.

TypeScript can rescue your ass. It can give you a warning a little bit earlier than during runtime. Where it may otherwise crash in some circumstances if you forget one in one branch of your if statement.

Aside from that now we'll work on the front end of the book reading manager app whose back end we completed in the prior post. We won't make any API calls; instead, we'll use a basic array to hold the data.

Props and prop types

The goal is to make a book list component, so we'll make a new directory called "components" in the source folder. Next, we put the bookList.tsx file there to hold code for the book list component:

import React from "react";

const BookList: React.FC = () => {
  const books = [{ id: "1", title: "Adrenaline", writer: "Zlatan Ibrahimovic" }];

  return (
    <ul>
      <li>
        {books.map((book) => (
          <li key={book.id}>
            {book.title} - {book.writer}
          </li>
        ))}
      </li>
    </ul>
  );
};

export default BookList;

Still, this is not how you want your components to be. It might be acceptable for a first attempt.

So, let's change that in our App.tsx file as the component will receive data from outside:

import React from "react";
import BookList from "../components/bookList";

function App() {
  const books = [{ id: "1", title: "Adrenaline", writer: "Zlatan Ibrahimovic" }];

  return (
    <div className="App">
      <BookList books={books} />
    </div>
  );
}

export default App;

and our BookList should look like this:

import React from "react";

interface Book {
  id: string;
  title: string;
  writer: string;
}

interface BookListProps {
  books: Book[];
}

const BookList: React.FC<BookListProps> = (props) => {
  const { books } = props;

  return (
    <ul>
      <li>
        {books.map((book) => (
          <li key={book.id}>
            {book.title} - {book.writer}
          </li>
        ))}
      </li>
    </ul>
  );
};

export default BookList;

Take note of the following:

  • TypeScript allows us to design an interface that details our prop types. We use an array of books as our prop type in the BookList component because we receive an array of books in this case.
  • Since React.FC is a generic type, we may specify any type there to instruct our component's data structure for props.
  • VS Code will immediately report an error if the books parameter isn't passed to the BookList component in the App.tsx file.
  • We also receive the autocomplete feature when props are defined in this way. This means that if I type something wrong, like props.book instead of props.books, VS Code will give us an error.

So we get a lot of safety with using TypeScript here.

Book input management

We can create a new component called AddBook to allow users to add new books.

House of books
Photo by Florencia Viadana / Unsplash

To manage user input we can use React refs.

React refs are straightforward pointers to HTML DOM elements. They allow us to handle them and can be used to manage user input. The kind of HTML DOM element you are using the ref on must be specified when using TypeScript.

As an illustration, it is set to HTMLInputElement here because we will use it on the input field. But it can also be HTMLSelectElement, HTMLParagraphElement, etc.

import React, { useRef } from "react";

interface AddBookProps {
  onAddBookHandler: (title: string, writer: string) => void;
}

const AddBook: React.FC<AddBookProps> = (props) => {
  const titleRef = useRef<HTMLInputElement>(null);
  const writerRef = useRef<HTMLInputElement>(null);

  const submitBookHandler = (event: React.FormEvent) => {
    event.preventDefault();

    const title = titleRef.current?.value;
    const writer = writerRef.current?.value;
    console.log({ title, writer });

    if (!writer || !title) {
      return;
    }
    props.onAddBookHandler(title, writer);
  };

  return (
    <form onSubmit={submitBookHandler}>
      <div>
        <label htmlFor="title"></label>
        <input id="title" type="text" ref={titleRef} />
      </div>
      <div>
        <label htmlFor="writer"></label>
        <input id="writer" type="text" ref={writerRef} />
      </div>
      <button type="submit">Add book</button>
    </form>
  );
};

export default AddBook;

You can define props for this component by defining it in React.FC type, as we did before. As we get data from user input, we also use refs, which have a special react type of HTMLInputElement.

Additionally, this component gets a props method called onAddBookHandler. That function adds a new book to our backend and it takes 2 arguments (title and writer).

When a user clicks submit, the submitBookHandler function receives a FormEvent event. At that point, we validate the data before sending it.

Component communication

In a genuine project, your state management and component communication will take place in a centralized location.

So let's implement the deletion and update of the read state of books in our BookList component:

import React from "react";
import { Book } from "../bookModel";

interface BookListProps {
  books: Book[];
  onDeleteBookHandler: (bookId: string) => void;
  onToggleReadBookHandler: (bookId: string) => void;
}

const BookList: React.FC<BookListProps> = (props) => {
  const { books, onDeleteBookHandler, onToggleReadBookHandler } = props;

  return (
    <ul>
      {books.map((book) => (
        <li key={book.id}>
          <span>
            {book.title} - {book.writer}
          </span>
          <button onClick={() => onDeleteBookHandler(book.id)}>Delete</button>
          <button onClick={() => onToggleReadBookHandler(book.id)}>
            Mark as {book.isRead ? "not read" : "read"}
          </button>
        </li>
      ))}
    </ul>
  );
};

export default BookList;

With this, we can also apply changes to our App.tsx file:

import React, { useState } from "react";
import BookList from "./components/bookList";
import AddBook from "./components/addBook";
import { Book } from "./bookModel";

function App() {
  const [books, setBooks] = useState<Book[]>([]);

  const addBookHandler = (title: string, writer: string) => {
    setBooks((prevBooks) => [
      ...books,
      { id: Math.random().toString(), title, writer, isRead: false }
    ]);
  };

  const deleteBookHandler = (bookId: string) => {
    setBooks((prevBooks) => prevBooks.filter((book) => book.id !== bookId));
  };

  const toggleReadBookHandler = (bookId: string) => {
    setBooks((prevBooks) =>
      prevBooks.map((book) => (book.id === bookId ? { ...book, isRead: !book.isRead } : book))
    );
  };

  return (
    <div className="App">
      <AddBook onAddBookHandler={addBookHandler} />
      <BookList
        books={books}
        onDeleteBookHandler={deleteBookHandler}
        onToggleReadBookHandler={toggleReadBookHandler}
      />
    </div>
  );
}

export default App;

As you can see, we removed the hard-coded data and are now storing it using the useState hook. useState is also flexible and supports any form of data you want to store. We use the same syntax as before to describe the type of data we will store in the state. And this is where we have a variety of books stored.

We provide the component with the deleteBookHandler function to allow for the deletion of books. This function accepts the book ID. Then, using the .filter function, we replace the array that already contains books with that ID with a new one that doesn't.

The toggleReadBookHandler function does the same thing. There, we change the read state of a single instance of the book to a new boolean value. This is accomplished using the .map method. There we determine whether the book ID matches the one we want to update. If the answer is yes, we update the isRead property with the new value, with the spread operator. If the answer is no, we use the original object.

And that's it, our little project is finished. The entire project code is available here.

Conclusion

So, we examined React and TypeScript in this article.

As you can see, TypeScript can offer many useful extra features. It also offers a lot of tests that ensure we produce clear and error-free code.

Now, I did show you a few patterns there, as well as how to leverage some key React capabilities like props and state, in combination with TypeScript.

Keep in mind everything else you learned about TypeScript. All the types you have studied, and everything TypeScript is capable of.  Everything you learned applies to this TypeScript and React project.  

Even though we're using JSX and React, we're still writing JavaScript. Or more particularly TypeScript. All the information you get in this article series is applicable here as well.

It's simple to forget that, so be mindful of it at all times. After that, think about using TypeScript to create your next React project. You now understand how to begin with it and how it operates.